+6
appview/config.go
+6
appview/config.go
···
36
36
SharedSecret string `env:"SHARED_SECRET"`
37
37
}
38
38
39
+
type PosthogConfig struct {
40
+
ApiKey string `env:"API_KEY"`
41
+
Endpoint string `env:"ENDPOINT, default=https://eu.i.posthog.com"`
42
+
}
43
+
39
44
type Config struct {
40
45
Core CoreConfig `env:",prefix=TANGLED_"`
41
46
Jetstream JetstreamConfig `env:",prefix=TANGLED_JETSTREAM_"`
42
47
Resend ResendConfig `env:",prefix=TANGLED_RESEND_"`
48
+
Posthog PosthogConfig `env:",prefix=TANGLED_POSTHOG_"`
43
49
Camo CamoConfig `env:",prefix=TANGLED_CAMO_"`
44
50
Avatar AvatarConfig `env:",prefix=TANGLED_AVATAR_"`
45
51
OAuth OAuthConfig `env:",prefix=TANGLED_OAUTH_"`
+12
appview/oauth/handler/handler.go
+12
appview/oauth/handler/handler.go
···
12
12
"github.com/gorilla/sessions"
13
13
"github.com/haileyok/atproto-oauth-golang/helpers"
14
14
"github.com/lestrrat-go/jwx/v2/jwk"
15
+
"github.com/posthog/posthog-go"
15
16
"tangled.sh/tangled.sh/core/appview"
16
17
"tangled.sh/tangled.sh/core/appview/db"
17
18
"tangled.sh/tangled.sh/core/appview/knotclient"
···
34
35
Store *sessions.CookieStore
35
36
OAuth *oauth.OAuth
36
37
Enforcer *rbac.Enforcer
38
+
Posthog posthog.Client
37
39
}
38
40
39
41
func (o *OAuthHandler) Router() http.Handler {
···
247
249
248
250
log.Println("session saved successfully")
249
251
go o.addToDefaultKnot(oauthRequest.Did)
252
+
253
+
if !o.Config.Core.Dev {
254
+
err = o.Posthog.Enqueue(posthog.Capture{
255
+
DistinctId: oauthRequest.Did,
256
+
Event: "signin",
257
+
})
258
+
if err != nil {
259
+
log.Println("failed to enqueue posthog event:", err)
260
+
}
261
+
}
250
262
251
263
http.Redirect(w, r, "/", http.StatusFound)
252
264
}
+23
appview/state/follow.go
+23
appview/state/follow.go
···
7
7
8
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
9
lexutil "github.com/bluesky-social/indigo/lex/util"
10
+
"github.com/posthog/posthog-go"
10
11
"tangled.sh/tangled.sh/core/api/tangled"
11
12
"tangled.sh/tangled.sh/core/appview"
12
13
"tangled.sh/tangled.sh/core/appview/db"
···
70
71
FollowStatus: db.IsFollowing,
71
72
})
72
73
74
+
if !s.config.Core.Dev {
75
+
err = s.posthog.Enqueue(posthog.Capture{
76
+
DistinctId: currentUser.Did,
77
+
Event: "follow",
78
+
Properties: posthog.Properties{"subject": subjectIdent.DID.String()},
79
+
})
80
+
if err != nil {
81
+
log.Println("failed to enqueue posthog event:", err)
82
+
}
83
+
}
84
+
73
85
return
74
86
case http.MethodDelete:
75
87
// find the record in the db
···
100
112
UserDid: subjectIdent.DID.String(),
101
113
FollowStatus: db.IsNotFollowing,
102
114
})
115
+
116
+
if !s.config.Core.Dev {
117
+
err = s.posthog.Enqueue(posthog.Capture{
118
+
DistinctId: currentUser.Did,
119
+
Event: "unfollow",
120
+
Properties: posthog.Properties{"subject": subjectIdent.DID.String()},
121
+
})
122
+
if err != nil {
123
+
log.Println("failed to enqueue posthog event:", err)
124
+
}
125
+
}
103
126
104
127
return
105
128
}
+11
appview/state/profile.go
+11
appview/state/profile.go
···
15
15
"github.com/bluesky-social/indigo/atproto/syntax"
16
16
lexutil "github.com/bluesky-social/indigo/lex/util"
17
17
"github.com/go-chi/chi/v5"
18
+
"github.com/posthog/posthog-go"
18
19
"tangled.sh/tangled.sh/core/api/tangled"
19
20
"tangled.sh/tangled.sh/core/appview/db"
20
21
"tangled.sh/tangled.sh/core/appview/pages"
···
345
346
log.Println("failed to update profile", err)
346
347
s.pages.Notice(w, "update-profile", "Failed to update profile, try again later.")
347
348
return
349
+
}
350
+
351
+
if !s.config.Core.Dev {
352
+
err = s.posthog.Enqueue(posthog.Capture{
353
+
DistinctId: user.Did,
354
+
Event: "edit_profile",
355
+
})
356
+
if err != nil {
357
+
log.Println("failed to enqueue posthog event:", err)
358
+
}
348
359
}
349
360
350
361
s.pages.HxRedirect(w, "/"+user.Did)
+23
appview/state/pull.go
+23
appview/state/pull.go
···
28
28
lexutil "github.com/bluesky-social/indigo/lex/util"
29
29
"github.com/go-chi/chi/v5"
30
30
"github.com/google/uuid"
31
+
"github.com/posthog/posthog-go"
31
32
)
32
33
33
34
// htmx fragment
···
605
606
return
606
607
}
607
608
609
+
if !s.config.Core.Dev {
610
+
err = s.posthog.Enqueue(posthog.Capture{
611
+
DistinctId: user.Did,
612
+
Event: "new_pull_comment",
613
+
Properties: posthog.Properties{"repo_at": f.RepoAt.String(), "pull_id": pull.PullId},
614
+
})
615
+
if err != nil {
616
+
log.Println("failed to enqueue posthog event:", err)
617
+
}
618
+
}
619
+
608
620
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", f.OwnerSlashRepo(), pull.PullId, commentId))
609
621
return
610
622
}
···
980
992
log.Println("failed to create pull request", err)
981
993
s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.")
982
994
return
995
+
}
996
+
997
+
if !s.config.Core.Dev {
998
+
err = s.posthog.Enqueue(posthog.Capture{
999
+
DistinctId: user.Did,
1000
+
Event: "new_pull",
1001
+
Properties: posthog.Properties{"repo_at": f.RepoAt.String(), "pull_id": pullId},
1002
+
})
1003
+
if err != nil {
1004
+
log.Println("failed to enqueue posthog event:", err)
1005
+
}
983
1006
}
984
1007
985
1008
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullId))
+12
appview/state/repo.go
+12
appview/state/repo.go
···
33
33
securejoin "github.com/cyphar/filepath-securejoin"
34
34
"github.com/go-chi/chi/v5"
35
35
"github.com/go-git/go-git/v5/plumbing"
36
+
"github.com/posthog/posthog-go"
36
37
37
38
comatproto "github.com/bluesky-social/indigo/api/atproto"
38
39
lexutil "github.com/bluesky-social/indigo/lex/util"
···
1875
1876
log.Println("failed to set issue at", err)
1876
1877
s.pages.Notice(w, "issues", "Failed to create issue.")
1877
1878
return
1879
+
}
1880
+
1881
+
if !s.config.Core.Dev {
1882
+
err = s.posthog.Enqueue(posthog.Capture{
1883
+
DistinctId: user.Did,
1884
+
Event: "new_issue",
1885
+
Properties: posthog.Properties{"repo_at": f.RepoAt.String(), "issue_id": issueId},
1886
+
})
1887
+
if err != nil {
1888
+
log.Println("failed to enqueue posthog event:", err)
1889
+
}
1878
1890
}
1879
1891
1880
1892
s.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d", f.OwnerSlashRepo(), issueId))
+1
appview/state/router.go
+1
appview/state/router.go
+23
appview/state/star.go
+23
appview/state/star.go
···
8
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
9
"github.com/bluesky-social/indigo/atproto/syntax"
10
10
lexutil "github.com/bluesky-social/indigo/lex/util"
11
+
"github.com/posthog/posthog-go"
11
12
"tangled.sh/tangled.sh/core/api/tangled"
12
13
"tangled.sh/tangled.sh/core/appview"
13
14
"tangled.sh/tangled.sh/core/appview/db"
···
75
76
},
76
77
})
77
78
79
+
if !s.config.Core.Dev {
80
+
err = s.posthog.Enqueue(posthog.Capture{
81
+
DistinctId: currentUser.Did,
82
+
Event: "star",
83
+
Properties: posthog.Properties{"repo_at": subjectUri.String()},
84
+
})
85
+
if err != nil {
86
+
log.Println("failed to enqueue posthog event:", err)
87
+
}
88
+
}
89
+
78
90
return
79
91
case http.MethodDelete:
80
92
// find the record in the db
···
114
126
StarCount: starCount,
115
127
},
116
128
})
129
+
130
+
if !s.config.Core.Dev {
131
+
err = s.posthog.Enqueue(posthog.Capture{
132
+
DistinctId: currentUser.Did,
133
+
Event: "unstar",
134
+
Properties: posthog.Properties{"repo_at": subjectUri.String()},
135
+
})
136
+
if err != nil {
137
+
log.Println("failed to enqueue posthog event:", err)
138
+
}
139
+
}
117
140
118
141
return
119
142
}
+19
-76
appview/state/state.go
+19
-76
appview/state/state.go
···
17
17
lexutil "github.com/bluesky-social/indigo/lex/util"
18
18
securejoin "github.com/cyphar/filepath-securejoin"
19
19
"github.com/go-chi/chi/v5"
20
+
"github.com/posthog/posthog-go"
20
21
"tangled.sh/tangled.sh/core/api/tangled"
21
22
"tangled.sh/tangled.sh/core/appview"
22
23
"tangled.sh/tangled.sh/core/appview/db"
···
34
35
tidClock syntax.TIDClock
35
36
pages *pages.Pages
36
37
resolver *appview.Resolver
38
+
posthog posthog.Client
37
39
jc *jetstream.JetstreamClient
38
40
config *appview.Config
39
41
}
···
57
59
58
60
oauth := oauth.NewOAuth(d, config)
59
61
62
+
posthog, err := posthog.NewWithConfig(config.Posthog.ApiKey, posthog.Config{Endpoint: config.Posthog.Endpoint})
63
+
if err != nil {
64
+
return nil, fmt.Errorf("failed to create posthog client: %w", err)
65
+
}
66
+
60
67
wrapper := db.DbWrapper{d}
61
68
jc, err := jetstream.NewJetstreamClient(
62
69
config.Jetstream.Endpoint,
···
88
95
clock,
89
96
pgs,
90
97
resolver,
98
+
posthog,
91
99
jc,
92
100
config,
93
101
}
···
98
106
func TID(c *syntax.TIDClock) string {
99
107
return c.Next().String()
100
108
}
101
-
102
-
// func (s *State) Login(w http.ResponseWriter, r *http.Request) {
103
-
// ctx := r.Context()
104
-
105
-
// switch r.Method {
106
-
// case http.MethodGet:
107
-
// err := s.pages.Login(w, pages.LoginParams{})
108
-
// if err != nil {
109
-
// log.Printf("rendering login page: %s", err)
110
-
// }
111
-
112
-
// return
113
-
// case http.MethodPost:
114
-
// handle := strings.TrimPrefix(r.FormValue("handle"), "@")
115
-
// appPassword := r.FormValue("app_password")
116
-
117
-
// resolved, err := s.resolver.ResolveIdent(ctx, handle)
118
-
// if err != nil {
119
-
// log.Println("failed to resolve handle:", err)
120
-
// s.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle))
121
-
// return
122
-
// }
123
-
124
-
// atSession, err := s.oauth.CreateInitialSession(ctx, resolved, appPassword)
125
-
// if err != nil {
126
-
// s.pages.Notice(w, "login-msg", "Invalid handle or password.")
127
-
// return
128
-
// }
129
-
// sessionish := auth.CreateSessionWrapper{ServerCreateSession_Output: atSession}
130
-
131
-
// err = s.oauth.StoreSession(r, w, &sessionish, resolved.PDSEndpoint())
132
-
// if err != nil {
133
-
// s.pages.Notice(w, "login-msg", "Failed to login, try again later.")
134
-
// return
135
-
// }
136
-
137
-
// log.Printf("successfully saved session for %s (%s)", atSession.Handle, atSession.Did)
138
-
139
-
// did := resolved.DID.String()
140
-
// defaultKnot := "knot1.tangled.sh"
141
-
142
-
// go func() {
143
-
// log.Printf("adding %s to default knot", did)
144
-
// err = s.enforcer.AddMember(defaultKnot, did)
145
-
// if err != nil {
146
-
// log.Println("failed to add user to knot1.tangled.sh: ", err)
147
-
// return
148
-
// }
149
-
// err = s.enforcer.E.SavePolicy()
150
-
// if err != nil {
151
-
// log.Println("failed to add user to knot1.tangled.sh: ", err)
152
-
// return
153
-
// }
154
-
155
-
// secret, err := db.GetRegistrationKey(s.db, defaultKnot)
156
-
// if err != nil {
157
-
// log.Println("failed to get registration key for knot1.tangled.sh")
158
-
// return
159
-
// }
160
-
// signedClient, err := NewSignedClient(defaultKnot, secret, s.config.Core.Dev)
161
-
// resp, err := signedClient.AddMember(did)
162
-
// if err != nil {
163
-
// log.Println("failed to add user to knot1.tangled.sh: ", err)
164
-
// return
165
-
// }
166
-
167
-
// if resp.StatusCode != http.StatusNoContent {
168
-
// log.Println("failed to add user to knot1.tangled.sh: ", resp.StatusCode)
169
-
// return
170
-
// }
171
-
// }()
172
-
173
-
// s.pages.HxRedirect(w, "/")
174
-
// return
175
-
// }
176
-
// }
177
109
178
110
func (s *State) Logout(w http.ResponseWriter, r *http.Request) {
179
111
s.oauth.ClearSession(r, w)
···
773
705
log.Println("failed to update ACLs", err)
774
706
http.Error(w, err.Error(), http.StatusInternalServerError)
775
707
return
708
+
}
709
+
710
+
if !s.config.Core.Dev {
711
+
err = s.posthog.Enqueue(posthog.Capture{
712
+
DistinctId: user.Did,
713
+
Event: "new_repo",
714
+
Properties: posthog.Properties{"repo": repoName, "repo_at": repo.AtUri},
715
+
})
716
+
if err != nil {
717
+
log.Println("failed to enqueue posthog event:", err)
718
+
}
776
719
}
777
720
778
721
s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s", user.Handle, repoName))
+1
go.mod
+1
go.mod
···
24
24
github.com/lestrrat-go/jwx/v2 v2.0.12
25
25
github.com/mattn/go-sqlite3 v1.14.24
26
26
github.com/microcosm-cc/bluemonday v1.0.27
27
+
github.com/posthog/posthog-go v1.5.5
27
28
github.com/resend/resend-go/v2 v2.15.0
28
29
github.com/sethvargo/go-envconfig v1.1.0
29
30
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
+2
go.sum
+2
go.sum
···
224
224
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
225
225
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0=
226
226
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
227
+
github.com/posthog/posthog-go v1.5.5 h1:2o3j7IrHbTIfxRtj4MPaXKeimuTYg49onNzNBZbwksM=
228
+
github.com/posthog/posthog-go v1.5.5/go.mod h1:3RqUmSnPuwmeVj/GYrS75wNGqcAKdpODiwc83xZWgdE=
227
229
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
228
230
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
229
231
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=