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 oauthSess, err := s.oauthClient.ResumeSession(r.Context(), *did, sessionID)
118 if err != nil {
119 slog.Error("resuming session", "error", err, "did", *did, "session ID", sessionID)
120
121 // clear the session out
122 sess, _ := s.sessionStore.Get(r, sessionStoreName)
123 sess.Values = make(map[any]any)
124 _ = sess.Save(r, w)
125
126 http.Redirect(w, r, "/login", http.StatusFound)
127 return
128 }
129 c := oauthSess.APIClient()
130
131 createdAt := time.Now()
132
133 bodyReq := map[string]any{
134 "repo": c.AccountDID.String(),
135 "collection": "xyz.statusphere.status",
136 "record": map[string]any{
137 "status": status,
138 "createdAt": createdAt,
139 },
140 }
141 var result CreateRecordResp
142 err = c.Post(r.Context(), "com.atproto.repo.createRecord", bodyReq, &result)
143 if err != nil {
144 slog.Error("failed to create new status", "error", err)
145 http.Redirect(w, r, "/", http.StatusFound)
146 return
147 }
148
149 statusToStore := Status{
150 URI: result.URI,
151 Did: c.AccountDID.String(),
152 Status: status,
153 CreatedAt: createdAt.UnixMilli(),
154 IndexedAt: time.Now().UnixMilli(),
155 }
156
157 err = s.store.CreateStatus(statusToStore)
158 if err != nil {
159 slog.Error("failed to store status that has been created", "error", err)
160 }
161
162 http.Redirect(w, r, "/", http.StatusFound)
163}