An implementation of the ATProto statusphere example app but in Go
1package statusphere
2
3import (
4 "fmt"
5 "log/slog"
6 "net/http"
7 "time"
8)
9
10var Availablestatus = []string{
11 "👍",
12 "👎",
13 "💙",
14 "🥹",
15 "😧",
16 "😤",
17 "🙃",
18 "😉",
19 "😎",
20 "🤓",
21 "🤨",
22 "🥳",
23 "😭",
24 "😤",
25 "🤯",
26 "🫡",
27 "💀",
28 "✊",
29 "🤘",
30 "👀",
31 "🧠",
32 "👩💻",
33 "🧑💻",
34 "🥷",
35 "🧌",
36 "🦋",
37 "🚀",
38}
39
40type HomeData struct {
41 DisplayName string
42 AvailableStatus []string
43 UsersStatus []UserStatus
44}
45
46type UserStatus struct {
47 Status string
48 Handle string
49 HandleURL string
50 Date string
51 IsToday bool
52}
53
54func (s *Server) HandleHome(w http.ResponseWriter, r *http.Request) {
55 tmpl := s.getTemplate("home.html")
56 data := HomeData{
57 AvailableStatus: Availablestatus,
58 }
59
60 did, _ := s.currentSessionDID(r)
61 if did != nil {
62 profile, err := s.getUserProfileForDid(did.String())
63 if err != nil {
64 slog.Error("getting logged in users profile", "error", err)
65 }
66 data.DisplayName = profile.DisplayName
67 }
68
69 today := time.Now().Format(time.DateOnly)
70
71 results, err := s.store.GetStatuses(10)
72 if err != nil {
73 slog.Error("get status'", "error", err)
74 }
75
76 for _, status := range results {
77 date := time.UnixMilli(status.CreatedAt).Format(time.DateOnly)
78
79 profile, err := s.getUserProfileForDid(status.Did)
80 if err != nil {
81 slog.Error("getting user profile for status - skipping", "error", err, "did", status.Did)
82 continue
83 }
84
85 data.UsersStatus = append(data.UsersStatus, UserStatus{
86 Status: status.Status,
87 Handle: profile.Handle,
88 HandleURL: fmt.Sprintf("https://bsky.app/profile/%s", status.Did),
89 Date: date,
90 IsToday: date == today,
91 })
92 }
93
94 tmpl.Execute(w, data)
95}
96
97func (s *Server) HandleStatus(w http.ResponseWriter, r *http.Request) {
98 err := r.ParseForm()
99 if err != nil {
100 slog.Error("parsing form", "error", err)
101 http.Error(w, "parsing form", http.StatusBadRequest)
102 return
103 }
104
105 status := r.FormValue("status")
106 if status == "" {
107 http.Error(w, "missing status", http.StatusBadRequest)
108 return
109 }
110
111 did, sessionID := s.currentSessionDID(r)
112 if did == nil {
113 http.Redirect(w, r, "/login", http.StatusFound)
114 return
115 }
116
117 slog.Info("session", "did", did.String(), "session id", sessionID)
118
119 oauthSess, err := s.oauthClient.ResumeSession(r.Context(), *did, sessionID)
120 if err != nil {
121 http.Error(w, "not authenticated", http.StatusUnauthorized)
122 return
123 }
124 c := oauthSess.APIClient()
125
126 createdAt := time.Now()
127
128 bodyReq := map[string]any{
129 "repo": c.AccountDID.String(),
130 "collection": "xyz.statusphere.status",
131 "record": map[string]any{
132 "status": status,
133 "createdAt": createdAt,
134 },
135 }
136 var result CreateRecordResp
137 err = c.Post(r.Context(), "com.atproto.repo.createRecord", bodyReq, &result)
138 if err != nil {
139 slog.Error("failed to create new status", "error", err)
140 http.Redirect(w, r, "/", http.StatusFound)
141 return
142 }
143
144 statusToStore := Status{
145 URI: result.URI,
146 Did: c.AccountDID.String(),
147 Status: status,
148 CreatedAt: createdAt.UnixMilli(),
149 IndexedAt: time.Now().UnixMilli(),
150 }
151
152 err = s.store.CreateStatus(statusToStore)
153 if err != nil {
154 slog.Error("failed to store status that has been created", "error", err)
155 }
156
157 http.Redirect(w, r, "/", http.StatusFound)
158}