- {{ template "feature" (list
- (dict
- "title" "lightweight git repo hosting"
- "image" "https://assets.tangled.network/what-is-tangled-repo.png"
- "alt" "A repository hosted on Tangled"
- )
- (list
- "Host your repositories on your own infrastructure using
—tiny, headless servers that facilitate git operations."
- "Add friends to your knot or invite collaborators to your repository."
- "Guarded by fine-grained role-based access control."
- "Use SSH to push and pull."
- )
- ) }}
-
- {{ template "feature" (list
- (dict
- "title" "improved pull request model"
- "image" "https://assets.tangled.network/pulls.png"
- "alt" "Round-based pull requests."
- )
- (list
- "An intuitive and effective round-based pull request flow, with inter-diffing between rounds."
- "Stacked pull requests using Jujutsu's change IDs."
- "Paste a
for quick drive-by changes."
- )
- ) }}
-
- {{ template "feature" (list
- (dict
- "title" "run pipelines using spindles"
- "image" "https://assets.tangled.network/pipelines.png"
- "alt" "CI pipeline running on spindle"
- )
- (list
- "Run pipelines on your own infrastructure using
—lightweight CI runners."
- "Natively supports Nix for package management."
- "Easily extended to support different execution backends."
- )
- ) }}
+{{ define "post" }}
+
+
+
Join the network
+
+ You can participate in the Tangled network with an AT account. If you
+ don't know what that is, you can sign up for one below.
+
+ Read more on the docs.
+
+
{{ end }}
diff --git a/appview/state/state.go b/appview/state/state.go
index 954dec49..2e9ce266 100644
--- a/appview/state/state.go
+++ b/appview/state/state.go
@@ -251,50 +251,6 @@ func (s *State) Brand(w http.ResponseWriter, r *http.Request) {
})
}
-func (s *State) HomeOrTimeline(w http.ResponseWriter, r *http.Request) {
- if s.oauth.GetMultiAccountUser(r) != nil {
- s.Timeline(w, r)
- return
- }
- s.Home(w, r)
-}
-
-func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
- user := s.oauth.GetMultiAccountUser(r)
-
- // TODO: set this flag based on the UI
- filtered := false
-
- var userDid string
- if user != nil && user.Active != nil {
- userDid = user.Active.Did
- }
- timeline, err := db.MakeTimeline(s.db, 50, userDid, filtered)
- if err != nil {
- s.logger.Error("failed to make timeline", "err", err)
- s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
- }
-
- repos, err := db.GetTopStarredReposLastWeek(s.db)
- if err != nil {
- s.logger.Error("failed to get top starred repos", "err", err)
- s.pages.Notice(w, "topstarredrepos", "Unable to load.")
- return
- }
-
- gfiLabel, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", s.config.Label.GoodFirstIssue))
- if err != nil {
- // non-fatal
- }
-
- s.pages.Timeline(w, pages.TimelineParams{
- LoggedInUser: user,
- Timeline: timeline,
- Repos: repos,
- GfiLabel: gfiLabel,
- })
-}
-
func (s *State) UpgradeBanner(w http.ResponseWriter, r *http.Request) {
user := s.oauth.GetMultiAccountUser(r)
if user == nil {
@@ -332,31 +288,6 @@ func (s *State) UpgradeBanner(w http.ResponseWriter, r *http.Request) {
})
}
-func (s *State) Home(w http.ResponseWriter, r *http.Request) {
- // TODO: set this flag based on the UI
- filtered := false
-
- timeline, err := db.MakeTimeline(s.db, 5, "", filtered)
- if err != nil {
- s.logger.Error("failed to make timeline", "err", err)
- s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
- return
- }
-
- repos, err := db.GetTopStarredReposLastWeek(s.db)
- if err != nil {
- s.logger.Error("failed to get top starred repos", "err", err)
- s.pages.Notice(w, "topstarredrepos", "Unable to load.")
- return
- }
-
- s.pages.Home(w, pages.TimelineParams{
- LoggedInUser: nil,
- Timeline: timeline,
- Repos: repos,
- })
-}
-
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
user := chi.URLParam(r, "user")
user = strings.TrimPrefix(user, "@")
@@ -676,3 +607,54 @@ func BackfillDefaultDefs(e db.Execer, r *idresolver.Resolver, defaults []string)
return nil
}
+
+func fetchBskyPosts(ctx context.Context, res *idresolver.Resolver, config *config.Config, d *db.DB, logger *slog.Logger) {
+ resolved, err := res.ResolveIdent(context.Background(), consts.TangledDid)
+ if err != nil {
+ logger.Error("failed to resolve tangled.org DID", "err", err)
+ return
+ }
+
+ pdsEndpoint := resolved.PDSEndpoint()
+ if pdsEndpoint == "" {
+ logger.Error("no PDS endpoint found for tangled.sh DID")
+ return
+ }
+
+ session, err := oauth.CreateAppPasswordSession(res, config.Core.AppPassword, consts.TangledDid)
+ if err != nil {
+ logger.Error("failed to create appassword session... skipping fetch", "err", err)
+ return
+ }
+
+ client := xrpc.Client{
+ Auth: &xrpc.AuthInfo{
+ AccessJwt: session.AccessJwt,
+ Did: session.Did,
+ },
+ Host: session.PdsEndpoint,
+ }
+
+ l := log.SubLogger(logger, "bluesky")
+
+ ticker := time.NewTicker(config.Bluesky.UpdateInterval)
+ defer ticker.Stop()
+
+ for {
+ posts, _, err := bsky.FetchPosts(ctx, &client, 20, "")
+ if err != nil {
+ l.Error("failed to fetch bluesky posts", "err", err)
+ } else if err := db.InsertBlueskyPosts(d, posts); err != nil {
+ l.Error("failed to insert bluesky posts", "err", err)
+ } else {
+ l.Info("inserted bluesky posts", "count", len(posts))
+ }
+
+ select {
+ case <-ticker.C:
+ case <-ctx.Done():
+ l.Info("stopping bluesky updater")
+ return
+ }
+ }
+}
diff --git a/appview/state/timeline.go b/appview/state/timeline.go
new file mode 100644
index 00000000..ec736b58
--- /dev/null
+++ b/appview/state/timeline.go
@@ -0,0 +1,76 @@
+package state
+
+import (
+ "fmt"
+ "net/http"
+
+ "tangled.org/core/appview/db"
+ "tangled.org/core/appview/pages"
+ "tangled.org/core/orm"
+)
+
+func (s *State) Home(w http.ResponseWriter, r *http.Request) {
+ // TODO: set this flag based on the UI
+ filtered := false
+
+ timeline, err := db.MakeTimeline(s.db, 50, "", filtered)
+ if err != nil {
+ s.logger.Error("failed to make timeline", "err", err)
+ s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
+ return
+ }
+
+ blueskyPosts, err := db.GetBlueskyPosts(s.db, 8)
+ if err != nil {
+ s.logger.Error("failed to get bluesky posts", "err", err)
+ }
+
+ fmt.Println(s.pages.Home(w, pages.TimelineParams{
+ LoggedInUser: nil,
+ Timeline: timeline,
+ BlueskyPosts: blueskyPosts,
+ }))
+}
+func (s *State) HomeOrTimeline(w http.ResponseWriter, r *http.Request) {
+ if s.oauth.GetMultiAccountUser(r) != nil {
+ s.Timeline(w, r)
+ return
+ }
+ s.Home(w, r)
+}
+
+func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
+ user := s.oauth.GetMultiAccountUser(r)
+
+ // TODO: set this flag based on the UI
+ filtered := false
+
+ var userDid string
+ if user != nil && user.Active != nil {
+ userDid = user.Active.Did
+ }
+ timeline, err := db.MakeTimeline(s.db, 50, userDid, filtered)
+ if err != nil {
+ s.logger.Error("failed to make timeline", "err", err)
+ s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
+ }
+
+ repos, err := db.GetTopStarredReposLastWeek(s.db)
+ if err != nil {
+ s.logger.Error("failed to get top starred repos", "err", err)
+ s.pages.Notice(w, "topstarredrepos", "Unable to load.")
+ return
+ }
+
+ gfiLabel, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", s.config.Label.GoodFirstIssue))
+ if err != nil {
+ // non-fatal
+ }
+
+ s.pages.Timeline(w, pages.TimelineParams{
+ LoggedInUser: user,
+ Timeline: timeline,
+ Repos: repos,
+ GfiLabel: gfiLabel,
+ })
+}