1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "strings"
8
9 "github.com/bluesky-social/indigo/api/agnostic"
10 comatproto "github.com/bluesky-social/indigo/api/atproto"
11 _ "github.com/bluesky-social/indigo/api/bsky"
12 "github.com/bluesky-social/indigo/atproto/data"
13 "github.com/bluesky-social/indigo/atproto/identity"
14 "github.com/bluesky-social/indigo/atproto/syntax"
15 "github.com/bluesky-social/indigo/xrpc"
16
17 "github.com/flosch/pongo2/v6"
18 "github.com/labstack/echo/v4"
19)
20
21func (srv *Server) WebHome(c echo.Context) error {
22 info := pongo2.Context{}
23 return c.Render(http.StatusOK, "home.html", info)
24}
25
26func (srv *Server) WebQuery(c echo.Context) error {
27
28 // parse the q query param, redirect based on that
29 q := c.QueryParam("q")
30 if q == "" {
31 return c.Redirect(http.StatusFound, "/")
32 }
33 if strings.HasPrefix(q, "https://") {
34 q = ParseServiceURL(q)
35 }
36 if strings.HasPrefix(q, "at://") {
37 if strings.HasSuffix(q, "/") {
38 q = q[0 : len(q)-1]
39 }
40
41 aturi, err := syntax.ParseATURI(q)
42 if err != nil {
43 return err
44 }
45 if aturi.RecordKey() != "" {
46 return c.Redirect(http.StatusFound, fmt.Sprintf("/at/%s/%s/%s", aturi.Authority(), aturi.Collection(), aturi.RecordKey()))
47 }
48 if aturi.Collection() != "" {
49 return c.Redirect(http.StatusFound, fmt.Sprintf("/at/%s/%s", aturi.Authority(), aturi.Collection()))
50 }
51 return c.Redirect(http.StatusFound, fmt.Sprintf("/at/%s", aturi.Authority()))
52 }
53 if strings.HasPrefix(q, "did:") {
54 return c.Redirect(http.StatusFound, fmt.Sprintf("/account/%s", q))
55 }
56 _, err := syntax.ParseHandle(q)
57 if nil == err {
58 return c.Redirect(http.StatusFound, fmt.Sprintf("/account/%s", q))
59 }
60 return echo.NewHTTPError(400, "failed to parse query")
61}
62
63// e.GET("/account/:atid", srv.WebAccount)
64func (srv *Server) WebAccount(c echo.Context) error {
65 ctx := c.Request().Context()
66 //req := c.Request()
67 info := pongo2.Context{}
68
69 atid, err := syntax.ParseAtIdentifier(c.Param("atid"))
70 if err != nil {
71 return echo.NewHTTPError(404, "failed to parse handle or DID")
72 }
73
74 ident, err := srv.dir.Lookup(ctx, *atid)
75 if err != nil {
76 // TODO: proper error page?
77 return err
78 }
79
80 bdir := identity.BaseDirectory{}
81 doc, err := bdir.ResolveDID(ctx, ident.DID)
82 if nil == err {
83 b, err := json.MarshalIndent(doc, "", " ")
84 if err != nil {
85 return err
86 }
87 info["didDocJSON"] = string(b)
88 }
89 info["atid"] = atid
90 info["ident"] = ident
91 info["uri"] = atid
92 return c.Render(http.StatusOK, "account.html", info)
93}
94
95// e.GET("/at/:atid", srv.WebRepo)
96func (srv *Server) WebRepo(c echo.Context) error {
97 ctx := c.Request().Context()
98 //req := c.Request()
99 info := pongo2.Context{}
100
101 atid, err := syntax.ParseAtIdentifier(c.Param("atid"))
102 if err != nil {
103 return echo.NewHTTPError(400, "failed to parse handle or DID")
104 }
105
106 ident, err := srv.dir.Lookup(ctx, *atid)
107 if err != nil {
108 // TODO: proper error page?
109 return err
110 }
111 info["atid"] = atid
112 info["ident"] = ident
113 info["uri"] = fmt.Sprintf("at://%s", atid)
114
115 // create a new API client to connect to the account's PDS
116 xrpcc := xrpc.Client{
117 Host: ident.PDSEndpoint(),
118 }
119 if xrpcc.Host == "" {
120 return fmt.Errorf("no PDS endpoint for identity")
121 }
122
123 desc, err := comatproto.RepoDescribeRepo(ctx, &xrpcc, ident.DID.String())
124 if err != nil {
125 return err
126 }
127 info["collections"] = desc.Collections
128
129 return c.Render(http.StatusOK, "repo.html", info)
130}
131
132// e.GET("/at/:atid/:collection", srv.WebCollection)
133func (srv *Server) WebRepoCollection(c echo.Context) error {
134 ctx := c.Request().Context()
135 //req := c.Request()
136 info := pongo2.Context{}
137
138 atid, err := syntax.ParseAtIdentifier(c.Param("atid"))
139 if err != nil {
140 return echo.NewHTTPError(400, "failed to parse handle or DID")
141 }
142
143 collection, err := syntax.ParseNSID(c.Param("collection"))
144 if err != nil {
145 return echo.NewHTTPError(400, "failed to parse collection NSID")
146 }
147
148 ident, err := srv.dir.Lookup(ctx, *atid)
149 if err != nil {
150 // TODO: proper error page?
151 return err
152 }
153 info["atid"] = atid
154 info["ident"] = ident
155 info["collection"] = collection
156 info["uri"] = fmt.Sprintf("at://%s/%s", atid, collection)
157
158 // create a new API client to connect to the account's PDS
159 xrpcc := xrpc.Client{
160 Host: ident.PDSEndpoint(),
161 }
162 if xrpcc.Host == "" {
163 return fmt.Errorf("no PDS endpoint for identity")
164 }
165
166 cursor := c.QueryParam("cursor")
167 // collection string, cursor string, limit int64, repo string, reverse bool
168 resp, err := agnostic.RepoListRecords(ctx, &xrpcc, collection.String(), cursor, 100, ident.DID.String(), false)
169 if err != nil {
170 return err
171 }
172 recordURIs := make([]syntax.ATURI, len(resp.Records))
173 for i, rec := range resp.Records {
174 aturi, err := syntax.ParseATURI(rec.Uri)
175 if err != nil {
176 return err
177 }
178 recordURIs[i] = aturi
179 }
180 if resp.Cursor != nil && *resp.Cursor != "" {
181 cursor = *resp.Cursor
182 }
183
184 info["records"] = resp.Records
185 info["recordURIs"] = recordURIs
186 info["cursor"] = cursor
187 return c.Render(http.StatusOK, "repo_collection.html", info)
188}
189
190// e.GET("/at/:atid/:collection/:rkey", srv.WebRecord)
191func (srv *Server) WebRepoRecord(c echo.Context) error {
192 ctx := c.Request().Context()
193 //req := c.Request()
194 info := pongo2.Context{}
195
196 atid, err := syntax.ParseAtIdentifier(c.Param("atid"))
197 if err != nil {
198 return echo.NewHTTPError(400, "failed to parse handle or DID")
199 }
200
201 collection, err := syntax.ParseNSID(c.Param("collection"))
202 if err != nil {
203 return echo.NewHTTPError(400, "failed to parse collection NSID")
204 }
205
206 rkey, err := syntax.ParseRecordKey(c.Param("rkey"))
207 if err != nil {
208 return echo.NewHTTPError(400, "failed to parse record key")
209 }
210
211 ident, err := srv.dir.Lookup(ctx, *atid)
212 if err != nil {
213 // TODO: proper error page?
214 return err
215 }
216 info["atid"] = atid
217 info["ident"] = ident
218 info["collection"] = collection
219 info["rkey"] = rkey
220 info["uri"] = fmt.Sprintf("at://%s/%s/%s", atid, collection, rkey)
221
222 xrpcc := xrpc.Client{
223 Host: ident.PDSEndpoint(),
224 }
225 resp, err := agnostic.RepoGetRecord(ctx, &xrpcc, "", collection.String(), ident.DID.String(), rkey.String())
226 if err != nil {
227 return echo.NewHTTPError(400, fmt.Sprintf("failed to load record: %s", err))
228 }
229
230 if nil == resp.Value {
231 return fmt.Errorf("empty record in response")
232 }
233
234 record, err := data.UnmarshalJSON(*resp.Value)
235 if err != nil {
236 return fmt.Errorf("fetched record was invalid data: %w", err)
237 }
238 info["record"] = record
239
240 b, err := json.MarshalIndent(record, "", " ")
241 if err != nil {
242 return err
243 }
244 info["recordJSON"] = string(b)
245
246 return c.Render(http.StatusOK, "repo_record.html", info)
247}