+20
-190
appview/db/profile.go
+20
-190
appview/db/profile.go
···
10
10
"time"
11
11
12
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
-
"tangled.org/core/api/tangled"
14
13
"tangled.org/core/appview/models"
15
14
)
16
15
17
-
type RepoEvent struct {
18
-
Repo *models.Repo
19
-
Source *models.Repo
20
-
}
21
-
22
-
type ProfileTimeline struct {
23
-
ByMonth []ByMonth
24
-
}
25
-
26
-
func (p *ProfileTimeline) IsEmpty() bool {
27
-
if p == nil {
28
-
return true
29
-
}
30
-
31
-
for _, m := range p.ByMonth {
32
-
if !m.IsEmpty() {
33
-
return false
34
-
}
35
-
}
36
-
37
-
return true
38
-
}
39
-
40
-
type ByMonth struct {
41
-
RepoEvents []RepoEvent
42
-
IssueEvents IssueEvents
43
-
PullEvents PullEvents
44
-
}
45
-
46
-
func (b ByMonth) IsEmpty() bool {
47
-
return len(b.RepoEvents) == 0 &&
48
-
len(b.IssueEvents.Items) == 0 &&
49
-
len(b.PullEvents.Items) == 0
50
-
}
51
-
52
-
type IssueEvents struct {
53
-
Items []*models.Issue
54
-
}
55
-
56
-
type IssueEventStats struct {
57
-
Open int
58
-
Closed int
59
-
}
60
-
61
-
func (i IssueEvents) Stats() IssueEventStats {
62
-
var open, closed int
63
-
for _, issue := range i.Items {
64
-
if issue.Open {
65
-
open += 1
66
-
} else {
67
-
closed += 1
68
-
}
69
-
}
70
-
71
-
return IssueEventStats{
72
-
Open: open,
73
-
Closed: closed,
74
-
}
75
-
}
76
-
77
-
type PullEvents struct {
78
-
Items []*models.Pull
79
-
}
80
-
81
-
func (p PullEvents) Stats() PullEventStats {
82
-
var open, merged, closed int
83
-
for _, pull := range p.Items {
84
-
switch pull.State {
85
-
case models.PullOpen:
86
-
open += 1
87
-
case models.PullMerged:
88
-
merged += 1
89
-
case models.PullClosed:
90
-
closed += 1
91
-
}
92
-
}
93
-
94
-
return PullEventStats{
95
-
Open: open,
96
-
Merged: merged,
97
-
Closed: closed,
98
-
}
99
-
}
100
-
101
-
type PullEventStats struct {
102
-
Closed int
103
-
Open int
104
-
Merged int
105
-
}
106
-
107
16
const TimeframeMonths = 7
108
17
109
-
func MakeProfileTimeline(e Execer, forDid string) (*ProfileTimeline, error) {
110
-
timeline := ProfileTimeline{
111
-
ByMonth: make([]ByMonth, TimeframeMonths),
18
+
func MakeProfileTimeline(e Execer, forDid string) (*models.ProfileTimeline, error) {
19
+
timeline := models.ProfileTimeline{
20
+
ByMonth: make([]models.ByMonth, TimeframeMonths),
112
21
}
113
22
currentMonth := time.Now().Month()
114
23
timeframe := fmt.Sprintf("-%d months", TimeframeMonths)
···
181
90
idx := currentMonth - repoMonth
182
91
183
92
items := &timeline.ByMonth[idx].RepoEvents
184
-
*items = append(*items, RepoEvent{
93
+
*items = append(*items, models.RepoEvent{
185
94
Repo: &repo,
186
95
Source: sourceRepo,
187
96
})
···
190
99
return &timeline, nil
191
100
}
192
101
193
-
type Profile struct {
194
-
// ids
195
-
ID int
196
-
Did string
197
-
198
-
// data
199
-
Description string
200
-
IncludeBluesky bool
201
-
Location string
202
-
Links [5]string
203
-
Stats [2]VanityStat
204
-
PinnedRepos [6]syntax.ATURI
205
-
}
206
-
207
-
func (p Profile) IsLinksEmpty() bool {
208
-
for _, l := range p.Links {
209
-
if l != "" {
210
-
return false
211
-
}
212
-
}
213
-
return true
214
-
}
215
-
216
-
func (p Profile) IsStatsEmpty() bool {
217
-
for _, s := range p.Stats {
218
-
if s.Kind != "" {
219
-
return false
220
-
}
221
-
}
222
-
return true
223
-
}
224
-
225
-
func (p Profile) IsPinnedReposEmpty() bool {
226
-
for _, r := range p.PinnedRepos {
227
-
if r != "" {
228
-
return false
229
-
}
230
-
}
231
-
return true
232
-
}
233
-
234
-
type VanityStatKind string
235
-
236
-
const (
237
-
VanityStatMergedPRCount VanityStatKind = "merged-pull-request-count"
238
-
VanityStatClosedPRCount VanityStatKind = "closed-pull-request-count"
239
-
VanityStatOpenPRCount VanityStatKind = "open-pull-request-count"
240
-
VanityStatOpenIssueCount VanityStatKind = "open-issue-count"
241
-
VanityStatClosedIssueCount VanityStatKind = "closed-issue-count"
242
-
VanityStatRepositoryCount VanityStatKind = "repository-count"
243
-
)
244
-
245
-
func (v VanityStatKind) String() string {
246
-
switch v {
247
-
case VanityStatMergedPRCount:
248
-
return "Merged PRs"
249
-
case VanityStatClosedPRCount:
250
-
return "Closed PRs"
251
-
case VanityStatOpenPRCount:
252
-
return "Open PRs"
253
-
case VanityStatOpenIssueCount:
254
-
return "Open Issues"
255
-
case VanityStatClosedIssueCount:
256
-
return "Closed Issues"
257
-
case VanityStatRepositoryCount:
258
-
return "Repositories"
259
-
}
260
-
return ""
261
-
}
262
-
263
-
type VanityStat struct {
264
-
Kind VanityStatKind
265
-
Value uint64
266
-
}
267
-
268
-
func (p *Profile) ProfileAt() syntax.ATURI {
269
-
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.Did, tangled.ActorProfileNSID, "self"))
270
-
}
271
-
272
-
func UpsertProfile(tx *sql.Tx, profile *Profile) error {
102
+
func UpsertProfile(tx *sql.Tx, profile *models.Profile) error {
273
103
defer tx.Rollback()
274
104
275
105
// update links
···
367
197
return tx.Commit()
368
198
}
369
199
370
-
func GetProfiles(e Execer, filters ...filter) (map[string]*Profile, error) {
200
+
func GetProfiles(e Execer, filters ...filter) (map[string]*models.Profile, error) {
371
201
var conditions []string
372
202
var args []any
373
203
for _, filter := range filters {
···
397
227
return nil, err
398
228
}
399
229
400
-
profileMap := make(map[string]*Profile)
230
+
profileMap := make(map[string]*models.Profile)
401
231
for rows.Next() {
402
-
var profile Profile
232
+
var profile models.Profile
403
233
var includeBluesky int
404
234
405
235
err = rows.Scan(&profile.ID, &profile.Did, &profile.Description, &includeBluesky, &profile.Location)
···
470
300
return profileMap, nil
471
301
}
472
302
473
-
func GetProfile(e Execer, did string) (*Profile, error) {
474
-
var profile Profile
303
+
func GetProfile(e Execer, did string) (*models.Profile, error) {
304
+
var profile models.Profile
475
305
profile.Did = did
476
306
477
307
includeBluesky := 0
···
480
310
did,
481
311
).Scan(&profile.Description, &includeBluesky, &profile.Location)
482
312
if err == sql.ErrNoRows {
483
-
profile := Profile{}
313
+
profile := models.Profile{}
484
314
profile.Did = did
485
315
return &profile, nil
486
316
}
···
540
370
return &profile, nil
541
371
}
542
372
543
-
func GetVanityStat(e Execer, did string, stat VanityStatKind) (uint64, error) {
373
+
func GetVanityStat(e Execer, did string, stat models.VanityStatKind) (uint64, error) {
544
374
query := ""
545
375
var args []any
546
376
switch stat {
547
-
case VanityStatMergedPRCount:
377
+
case models.VanityStatMergedPRCount:
548
378
query = `select count(id) from pulls where owner_did = ? and state = ?`
549
379
args = append(args, did, models.PullMerged)
550
-
case VanityStatClosedPRCount:
380
+
case models.VanityStatClosedPRCount:
551
381
query = `select count(id) from pulls where owner_did = ? and state = ?`
552
382
args = append(args, did, models.PullClosed)
553
-
case VanityStatOpenPRCount:
383
+
case models.VanityStatOpenPRCount:
554
384
query = `select count(id) from pulls where owner_did = ? and state = ?`
555
385
args = append(args, did, models.PullOpen)
556
-
case VanityStatOpenIssueCount:
386
+
case models.VanityStatOpenIssueCount:
557
387
query = `select count(id) from issues where did = ? and open = 1`
558
388
args = append(args, did)
559
-
case VanityStatClosedIssueCount:
389
+
case models.VanityStatClosedIssueCount:
560
390
query = `select count(id) from issues where did = ? and open = 0`
561
391
args = append(args, did)
562
-
case VanityStatRepositoryCount:
392
+
case models.VanityStatRepositoryCount:
563
393
query = `select count(id) from repos where did = ?`
564
394
args = append(args, did)
565
395
}
···
573
403
return result, nil
574
404
}
575
405
576
-
func ValidateProfile(e Execer, profile *Profile) error {
406
+
func ValidateProfile(e Execer, profile *models.Profile) error {
577
407
// ensure description is not too long
578
408
if len(profile.Description) > 256 {
579
409
return fmt.Errorf("Entered bio is too long.")
···
621
451
return nil
622
452
}
623
453
624
-
func validateLinks(profile *Profile) error {
454
+
func validateLinks(profile *models.Profile) error {
625
455
for i, link := range profile.Links {
626
456
if link == "" {
627
457
continue
+1
-1
appview/db/timeline.go
+1
-1
appview/db/timeline.go
+3
-3
appview/ingester.go
+3
-3
appview/ingester.go
···
299
299
}
300
300
}
301
301
302
-
var stats [2]db.VanityStat
302
+
var stats [2]models.VanityStat
303
303
for i, s := range record.Stats {
304
304
if i < 2 {
305
-
stats[i].Kind = db.VanityStatKind(s)
305
+
stats[i].Kind = models.VanityStatKind(s)
306
306
}
307
307
}
308
308
···
313
313
}
314
314
}
315
315
316
-
profile := db.Profile{
316
+
profile := models.Profile{
317
317
Did: did,
318
318
Description: description,
319
319
IncludeBluesky: includeBluesky,
+177
appview/models/profile.go
+177
appview/models/profile.go
···
1
+
package models
2
+
3
+
import (
4
+
"fmt"
5
+
6
+
"github.com/bluesky-social/indigo/atproto/syntax"
7
+
"tangled.org/core/api/tangled"
8
+
)
9
+
10
+
type Profile struct {
11
+
// ids
12
+
ID int
13
+
Did string
14
+
15
+
// data
16
+
Description string
17
+
IncludeBluesky bool
18
+
Location string
19
+
Links [5]string
20
+
Stats [2]VanityStat
21
+
PinnedRepos [6]syntax.ATURI
22
+
}
23
+
24
+
func (p Profile) IsLinksEmpty() bool {
25
+
for _, l := range p.Links {
26
+
if l != "" {
27
+
return false
28
+
}
29
+
}
30
+
return true
31
+
}
32
+
33
+
func (p Profile) IsStatsEmpty() bool {
34
+
for _, s := range p.Stats {
35
+
if s.Kind != "" {
36
+
return false
37
+
}
38
+
}
39
+
return true
40
+
}
41
+
42
+
func (p Profile) IsPinnedReposEmpty() bool {
43
+
for _, r := range p.PinnedRepos {
44
+
if r != "" {
45
+
return false
46
+
}
47
+
}
48
+
return true
49
+
}
50
+
51
+
type VanityStatKind string
52
+
53
+
const (
54
+
VanityStatMergedPRCount VanityStatKind = "merged-pull-request-count"
55
+
VanityStatClosedPRCount VanityStatKind = "closed-pull-request-count"
56
+
VanityStatOpenPRCount VanityStatKind = "open-pull-request-count"
57
+
VanityStatOpenIssueCount VanityStatKind = "open-issue-count"
58
+
VanityStatClosedIssueCount VanityStatKind = "closed-issue-count"
59
+
VanityStatRepositoryCount VanityStatKind = "repository-count"
60
+
)
61
+
62
+
func (v VanityStatKind) String() string {
63
+
switch v {
64
+
case VanityStatMergedPRCount:
65
+
return "Merged PRs"
66
+
case VanityStatClosedPRCount:
67
+
return "Closed PRs"
68
+
case VanityStatOpenPRCount:
69
+
return "Open PRs"
70
+
case VanityStatOpenIssueCount:
71
+
return "Open Issues"
72
+
case VanityStatClosedIssueCount:
73
+
return "Closed Issues"
74
+
case VanityStatRepositoryCount:
75
+
return "Repositories"
76
+
}
77
+
return ""
78
+
}
79
+
80
+
type VanityStat struct {
81
+
Kind VanityStatKind
82
+
Value uint64
83
+
}
84
+
85
+
func (p *Profile) ProfileAt() syntax.ATURI {
86
+
return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", p.Did, tangled.ActorProfileNSID, "self"))
87
+
}
88
+
89
+
type RepoEvent struct {
90
+
Repo *Repo
91
+
Source *Repo
92
+
}
93
+
94
+
type ProfileTimeline struct {
95
+
ByMonth []ByMonth
96
+
}
97
+
98
+
func (p *ProfileTimeline) IsEmpty() bool {
99
+
if p == nil {
100
+
return true
101
+
}
102
+
103
+
for _, m := range p.ByMonth {
104
+
if !m.IsEmpty() {
105
+
return false
106
+
}
107
+
}
108
+
109
+
return true
110
+
}
111
+
112
+
type ByMonth struct {
113
+
RepoEvents []RepoEvent
114
+
IssueEvents IssueEvents
115
+
PullEvents PullEvents
116
+
}
117
+
118
+
func (b ByMonth) IsEmpty() bool {
119
+
return len(b.RepoEvents) == 0 &&
120
+
len(b.IssueEvents.Items) == 0 &&
121
+
len(b.PullEvents.Items) == 0
122
+
}
123
+
124
+
type IssueEvents struct {
125
+
Items []*Issue
126
+
}
127
+
128
+
type IssueEventStats struct {
129
+
Open int
130
+
Closed int
131
+
}
132
+
133
+
func (i IssueEvents) Stats() IssueEventStats {
134
+
var open, closed int
135
+
for _, issue := range i.Items {
136
+
if issue.Open {
137
+
open += 1
138
+
} else {
139
+
closed += 1
140
+
}
141
+
}
142
+
143
+
return IssueEventStats{
144
+
Open: open,
145
+
Closed: closed,
146
+
}
147
+
}
148
+
149
+
type PullEvents struct {
150
+
Items []*Pull
151
+
}
152
+
153
+
func (p PullEvents) Stats() PullEventStats {
154
+
var open, merged, closed int
155
+
for _, pull := range p.Items {
156
+
switch pull.State {
157
+
case PullOpen:
158
+
open += 1
159
+
case PullMerged:
160
+
merged += 1
161
+
case PullClosed:
162
+
closed += 1
163
+
}
164
+
}
165
+
166
+
return PullEventStats{
167
+
Open: open,
168
+
Merged: merged,
169
+
Closed: closed,
170
+
}
171
+
}
172
+
173
+
type PullEventStats struct {
174
+
Closed int
175
+
Open int
176
+
Merged int
177
+
}
+1
-1
appview/notify/merged_notifier.go
+1
-1
appview/notify/merged_notifier.go
···
62
62
}
63
63
}
64
64
65
-
func (m *mergedNotifier) UpdateProfile(ctx context.Context, profile *db.Profile) {
65
+
func (m *mergedNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {
66
66
for _, notifier := range m.notifiers {
67
67
notifier.UpdateProfile(ctx, profile)
68
68
}
+2
-2
appview/notify/notifier.go
+2
-2
appview/notify/notifier.go
···
21
21
NewPull(ctx context.Context, pull *models.Pull)
22
22
NewPullComment(ctx context.Context, comment *models.PullComment)
23
23
24
-
UpdateProfile(ctx context.Context, profile *db.Profile)
24
+
UpdateProfile(ctx context.Context, profile *models.Profile)
25
25
26
26
NewString(ctx context.Context, s *db.String)
27
27
EditString(ctx context.Context, s *db.String)
···
46
46
func (m *BaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {}
47
47
func (m *BaseNotifier) NewPullComment(ctx context.Context, models *models.PullComment) {}
48
48
49
-
func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *db.Profile) {}
49
+
func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {}
50
50
51
51
func (m *BaseNotifier) NewString(ctx context.Context, s *db.String) {}
52
52
func (m *BaseNotifier) EditString(ctx context.Context, s *db.String) {}
+5
-5
appview/pages/pages.go
+5
-5
appview/pages/pages.go
···
413
413
UserHandle string
414
414
FollowStatus models.FollowStatus
415
415
Punchcard *db.Punchcard
416
-
Profile *db.Profile
416
+
Profile *models.Profile
417
417
Stats ProfileStats
418
418
Active string
419
419
}
···
441
441
LoggedInUser *oauth.User
442
442
Repos []models.Repo
443
443
CollaboratingRepos []models.Repo
444
-
ProfileTimeline *db.ProfileTimeline
444
+
ProfileTimeline *models.ProfileTimeline
445
445
Card *ProfileCard
446
446
Active string
447
447
}
···
492
492
FollowStatus models.FollowStatus
493
493
FollowersCount int64
494
494
FollowingCount int64
495
-
Profile *db.Profile
495
+
Profile *models.Profile
496
496
}
497
497
498
498
type ProfileFollowersParams struct {
···
530
530
531
531
type EditBioParams struct {
532
532
LoggedInUser *oauth.User
533
-
Profile *db.Profile
533
+
Profile *models.Profile
534
534
}
535
535
536
536
func (p *Pages) EditBioFragment(w io.Writer, params EditBioParams) error {
···
539
539
540
540
type EditPinsParams struct {
541
541
LoggedInUser *oauth.User
542
-
Profile *db.Profile
542
+
Profile *models.Profile
543
543
AllRepos []PinnedRepo
544
544
}
545
545
+1
-1
appview/posthog/notifier.go
+1
-1
appview/posthog/notifier.go
···
121
121
}
122
122
}
123
123
124
-
func (n *posthogNotifier) UpdateProfile(ctx context.Context, profile *db.Profile) {
124
+
func (n *posthogNotifier) UpdateProfile(ctx context.Context, profile *models.Profile) {
125
125
err := n.client.Enqueue(posthog.Capture{
126
126
DistinctId: profile.Did,
127
127
Event: "edit_profile",
+7
-7
appview/state/profile.go
+7
-7
appview/state/profile.go
···
337
337
followStatus = models.IsSelf
338
338
}
339
339
340
-
var profile *db.Profile
340
+
var profile *models.Profile
341
341
if p, exists := profiles[did]; exists {
342
342
profile = p
343
343
} else {
344
-
profile = &db.Profile{}
344
+
profile = &models.Profile{}
345
345
profile.Did = did
346
346
}
347
347
followCards[i] = pages.FollowCard{
···
479
479
return nil
480
480
}
481
481
482
-
func (s *State) addRepoItems(ctx context.Context, feed *feeds.Feed, repos []db.RepoEvent, author *feeds.Author) error {
482
+
func (s *State) addRepoItems(ctx context.Context, feed *feeds.Feed, repos []models.RepoEvent, author *feeds.Author) error {
483
483
for _, repo := range repos {
484
484
item, err := s.createRepoItem(ctx, repo, author)
485
485
if err != nil {
···
508
508
}
509
509
}
510
510
511
-
func (s *State) createRepoItem(ctx context.Context, repo db.RepoEvent, author *feeds.Author) (*feeds.Item, error) {
511
+
func (s *State) createRepoItem(ctx context.Context, repo models.RepoEvent, author *feeds.Author) (*feeds.Item, error) {
512
512
var title string
513
513
if repo.Source != nil {
514
514
sourceOwner, err := s.idResolver.ResolveIdent(ctx, repo.Source.Did)
···
559
559
stat1 := r.FormValue("stat1")
560
560
561
561
if stat0 != "" {
562
-
profile.Stats[0].Kind = db.VanityStatKind(stat0)
562
+
profile.Stats[0].Kind = models.VanityStatKind(stat0)
563
563
}
564
564
565
565
if stat1 != "" {
566
-
profile.Stats[1].Kind = db.VanityStatKind(stat1)
566
+
profile.Stats[1].Kind = models.VanityStatKind(stat1)
567
567
}
568
568
569
569
if err := db.ValidateProfile(s.db, profile); err != nil {
···
614
614
s.updateProfile(profile, w, r)
615
615
}
616
616
617
-
func (s *State) updateProfile(profile *db.Profile, w http.ResponseWriter, r *http.Request) {
617
+
func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) {
618
618
user := s.oauth.GetUser(r)
619
619
tx, err := s.db.BeginTx(r.Context(), nil)
620
620
if err != nil {