- commit
- 351a5aa
- parent
- 20aaf47
- author
- Eric Bower
- date
- 2024-03-11 18:41:19 +0000 UTC
feat(ui): more api endpoints!
1 files changed,
+223,
-28
+223,
-28
1@@ -15,6 +15,14 @@ type registerPayload struct {
2 Name string `json:"name"`
3 }
4
5+func ensureUser(w http.ResponseWriter, user *db.User) bool {
6+ if user == nil {
7+ shared.JSONError(w, "User not found", http.StatusNotFound)
8+ return false
9+ }
10+ return true
11+}
12+
13 func registerUser(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey ssh.PublicKey, pubkeyStr string) http.HandlerFunc {
14 logger := httpCtx.Cfg.Logger
15 return func(w http.ResponseWriter, r *http.Request) {
16@@ -44,18 +52,15 @@ type featuresPayload struct {
17 Features []*db.FeatureFlag `json:"features"`
18 }
19
20-func getFeatures(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey string) http.HandlerFunc {
21+func getFeatures(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
22 logger := httpCtx.Cfg.Logger
23 return func(w http.ResponseWriter, r *http.Request) {
24 w.Header().Set("Content-Type", "application/json")
25-
26- dbpool := shared.GetDB(r)
27- user, err := dbpool.FindUserForKey("", pubkey)
28- if err != nil {
29- shared.JSONError(w, "User not found", http.StatusNotFound)
30+ if !ensureUser(w, user) {
31 return
32 }
33
34+ dbpool := shared.GetDB(r)
35 features, err := dbpool.FindFeaturesForUser(user.ID)
36 if err != nil {
37 shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
38@@ -72,21 +77,20 @@ func getFeatures(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey string) http.H
39 }
40 }
41
42-type rssTokenPayload struct {
43- Token string `json:"token"`
44+type tokenSecretPayload struct {
45+ Secret string `json:"secret"`
46 }
47
48-func findOrCreateRssToken(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey string) http.HandlerFunc {
49+func findOrCreateRssToken(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
50 logger := httpCtx.Cfg.Logger
51 return func(w http.ResponseWriter, r *http.Request) {
52 w.Header().Set("Content-Type", "application/json")
53- dbpool := shared.GetDB(r)
54- user, err := dbpool.FindUserForKey("", pubkey)
55- if err != nil {
56- shared.JSONError(w, "User not found", http.StatusUnprocessableEntity)
57+ if !ensureUser(w, user) {
58 return
59 }
60
61+ dbpool := shared.GetDB(r)
62+ var err error
63 rssToken, _ := dbpool.FindRssToken(user.ID)
64 if rssToken == "" {
65 rssToken, err = dbpool.InsertToken(user.ID, "pico-rss")
66@@ -96,7 +100,7 @@ func findOrCreateRssToken(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey strin
67 }
68 }
69
70- err = json.NewEncoder(w).Encode(&rssTokenPayload{Token: rssToken})
71+ err = json.NewEncoder(w).Encode(&tokenSecretPayload{Secret: rssToken})
72 if err != nil {
73 logger.Error(err.Error())
74 }
75@@ -107,17 +111,15 @@ type pubkeysPayload struct {
76 Pubkeys []*db.PublicKey `json:"pubkeys"`
77 }
78
79-func getPublicKeys(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey string) http.HandlerFunc {
80+func getPublicKeys(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
81 logger := httpCtx.Cfg.Logger
82 return func(w http.ResponseWriter, r *http.Request) {
83 w.Header().Set("Content-Type", "application/json")
84- dbpool := shared.GetDB(r)
85- user, err := dbpool.FindUserForKey("", pubkey)
86- if err != nil {
87- shared.JSONError(w, "User not found", http.StatusUnprocessableEntity)
88+ if !ensureUser(w, user) {
89 return
90 }
91
92+ dbpool := shared.GetDB(r)
93 pubkeys, err := dbpool.FindKeysForUser(user)
94 if err != nil {
95 shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
96@@ -140,21 +142,74 @@ func getPublicKeys(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey string) http
97 }
98 }
99
100-type tokensPayload struct {
101- Tokens []*db.Token `json:"tokens"`
102+type createPubkeyPayload struct {
103+ Pubkey string `json:"pubkey"`
104+ Name string `json:"name"`
105+}
106+
107+func createPubkey(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
108+ logger := httpCtx.Cfg.Logger
109+ return func(w http.ResponseWriter, r *http.Request) {
110+ w.Header().Set("Content-Type", "application/json")
111+ if !ensureUser(w, user) {
112+ return
113+ }
114+
115+ dbpool := shared.GetDB(r)
116+ var payload createPubkeyPayload
117+ body, _ := io.ReadAll(r.Body)
118+ _ = json.Unmarshal(body, &payload)
119+ err := dbpool.LinkUserKey(user.ID, payload.Pubkey, nil)
120+ if err != nil {
121+ shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
122+ return
123+ }
124+ pubkey, err := dbpool.FindPublicKeyForKey(payload.Pubkey)
125+ if err != nil {
126+ shared.JSONError(w, err.Error(), http.StatusNotFound)
127+ return
128+ }
129+
130+ err = json.NewEncoder(w).Encode(pubkey)
131+ if err != nil {
132+ logger.Error("json encode", "err", err.Error())
133+ }
134+ }
135 }
136
137-func getTokens(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey string) http.HandlerFunc {
138+func deletePubkey(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
139 logger := httpCtx.Cfg.Logger
140 return func(w http.ResponseWriter, r *http.Request) {
141 w.Header().Set("Content-Type", "application/json")
142+ if !ensureUser(w, user) {
143+ return
144+ }
145+ pubkeyID := shared.GetField(r, 0)
146+
147 dbpool := shared.GetDB(r)
148- user, err := dbpool.FindUserForKey("", pubkey)
149+ err := dbpool.RemoveKeys([]string{pubkeyID})
150 if err != nil {
151- shared.JSONError(w, "User not found", http.StatusUnprocessableEntity)
152+ logger.Error("could not remove pubkey", "err", err.Error())
153+ shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
154 return
155 }
156+ w.WriteHeader(http.StatusNoContent)
157+ }
158+}
159
160+type tokensPayload struct {
161+ Tokens []*db.Token `json:"tokens"`
162+}
163+
164+func getTokens(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
165+ logger := httpCtx.Cfg.Logger
166+ return func(w http.ResponseWriter, r *http.Request) {
167+ w.Header().Set("Content-Type", "application/json")
168+ if !ensureUser(w, user) {
169+ return
170+ }
171+
172+ dbpool := shared.GetDB(r)
173 tokens, err := dbpool.FindTokensForUser(user.ID)
174 if err != nil {
175 shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
176@@ -172,8 +227,138 @@ func getTokens(httpCtx *shared.HttpCtx, ctx ssh.Context, pubkey string) http.Han
177 }
178 }
179
180+type createTokenPayload struct {
181+ Name string `json:"name"`
182+}
183+
184+type createTokenResponsePayload struct {
185+ Secret string `json:"secret"`
186+ Token *db.Token `json:"token"`
187+}
188+
189+func createToken(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
190+ logger := httpCtx.Cfg.Logger
191+ return func(w http.ResponseWriter, r *http.Request) {
192+ w.Header().Set("Content-Type", "application/json")
193+ if !ensureUser(w, user) {
194+ return
195+ }
196+
197+ dbpool := shared.GetDB(r)
198+ var payload createTokenPayload
199+ body, _ := io.ReadAll(r.Body)
200+ _ = json.Unmarshal(body, &payload)
201+ secret, err := dbpool.InsertToken(user.ID, payload.Name)
202+ if err != nil {
203+ shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
204+ return
205+ }
206+
207+ // TODO: find token by name
208+ tokens, err := dbpool.FindTokensForUser(user.ID)
209+ if err != nil {
210+ shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
211+ }
212+
213+ var token *db.Token
214+ for _, tok := range tokens {
215+ if tok.Name == payload.Name {
216+ token = tok
217+ break
218+ }
219+ }
220+
221+ err = json.NewEncoder(w).Encode(&createTokenResponsePayload{Secret: secret, Token: token})
222+ if err != nil {
223+ logger.Error("json encode", "err", err.Error())
224+ }
225+ }
226+}
227+
228+func deleteToken(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
229+ logger := httpCtx.Cfg.Logger
230+ return func(w http.ResponseWriter, r *http.Request) {
231+ w.Header().Set("Content-Type", "application/json")
232+ if !ensureUser(w, user) {
233+ return
234+ }
235+ tokenID := shared.GetField(r, 0)
236+
237+ dbpool := shared.GetDB(r)
238+ err := dbpool.RemoveToken(tokenID)
239+ if err != nil {
240+ logger.Error("could not remove token", "err", err.Error())
241+ shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
242+ return
243+ }
244+ w.WriteHeader(http.StatusNoContent)
245+ }
246+}
247+
248+type projectsPayload struct {
249+ Projects []*db.Project `json:"projects"`
250+}
251+
252+func getProjects(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User) http.HandlerFunc {
253+ logger := httpCtx.Cfg.Logger
254+ return func(w http.ResponseWriter, r *http.Request) {
255+ w.Header().Set("Content-Type", "application/json")
256+ if !ensureUser(w, user) {
257+ return
258+ }
259+
260+ dbpool := shared.GetDB(r)
261+ projects, err := dbpool.FindProjectsByUser(user.ID)
262+ if err != nil {
263+ shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
264+ return
265+ }
266+
267+ if projects == nil {
268+ projects = []*db.Project{}
269+ }
270+
271+ err = json.NewEncoder(w).Encode(&projectsPayload{Projects: projects})
272+ if err != nil {
273+ logger.Error(err.Error())
274+ }
275+ }
276+}
277+
278+type postsPayload struct {
279+ Posts []*db.Post `json:"posts"`
280+}
281+
282+func getPosts(httpCtx *shared.HttpCtx, ctx ssh.Context, user *db.User, space string) http.HandlerFunc {
283+ logger := httpCtx.Cfg.Logger
284+ return func(w http.ResponseWriter, r *http.Request) {
285+ w.Header().Set("Content-Type", "application/json")
286+ if !ensureUser(w, user) {
287+ return
288+ }
289+
290+ dbpool := shared.GetDB(r)
291+ pages, err := dbpool.FindPostsForUser(&db.Pager{Num: 1000, Page: 0}, user.ID, space)
292+ if err != nil {
293+ shared.JSONError(w, err.Error(), http.StatusUnprocessableEntity)
294+ return
295+ }
296+
297+ posts := pages.Data
298+ if posts == nil {
299+ posts = []*db.Post{}
300+ }
301+
302+ err = json.NewEncoder(w).Encode(&postsPayload{Posts: posts})
303+ if err != nil {
304+ logger.Error(err.Error())
305+ }
306+ }
307+}
308+
309 func CreateRoutes(httpCtx *shared.HttpCtx, ctx ssh.Context) []shared.Route {
310 logger := httpCtx.Cfg.Logger
311+ dbpool := httpCtx.Dbpool
312 pubkey, err := shared.GetPublicKeyCtx(ctx)
313 if err != nil {
314 logger.Error("could not get pubkey from ctx", "err", err.Error())
315@@ -186,11 +371,21 @@ func CreateRoutes(httpCtx *shared.HttpCtx, ctx ssh.Context) []shared.Route {
316 return []shared.Route{}
317 }
318
319+ user, _ := dbpool.FindUserForKey("", pubkeyStr)
320+
321 return []shared.Route{
322 shared.NewCorsRoute("POST", "/api/users", registerUser(httpCtx, ctx, pubkey, pubkeyStr)),
323- shared.NewCorsRoute("GET", "/api/features", getFeatures(httpCtx, ctx, pubkeyStr)),
324- shared.NewCorsRoute("PUT", "/api/rss-token", findOrCreateRssToken(httpCtx, ctx, pubkeyStr)),
325- shared.NewCorsRoute("GET", "/api/pubkeys", getPublicKeys(httpCtx, ctx, pubkeyStr)),
326- shared.NewCorsRoute("GET", "/api/tokens", getTokens(httpCtx, ctx, pubkeyStr)),
327+ shared.NewCorsRoute("GET", "/api/features", getFeatures(httpCtx, ctx, user)),
328+ shared.NewCorsRoute("PUT", "/api/rss-token", findOrCreateRssToken(httpCtx, ctx, user)),
329+ shared.NewCorsRoute("GET", "/api/pubkeys", getPublicKeys(httpCtx, ctx, user)),
330+ shared.NewCorsRoute("POST", "/api/pubkeys", createPubkey(httpCtx, ctx, user)),
331+ shared.NewCorsRoute("DELETE", "/api/pubkeys/(.+)", deletePubkey(httpCtx, ctx, user)),
332+ shared.NewCorsRoute("GET", "/api/tokens", getTokens(httpCtx, ctx, user)),
333+ shared.NewCorsRoute("POST", "/api/tokens", createToken(httpCtx, ctx, user)),
334+ shared.NewCorsRoute("DELETE", "/api/tokens/(.+)", deleteToken(httpCtx, ctx, user)),
335+ shared.NewCorsRoute("GET", "/api/projects", getProjects(httpCtx, ctx, user)),
336+ shared.NewCorsRoute("GET", "/api/prose", getPosts(httpCtx, ctx, user, "prose")),
337+ shared.NewCorsRoute("GET", "/api/pastes", getPosts(httpCtx, ctx, user, "pastes")),
338+ shared.NewCorsRoute("GET", "/api/feeds", getPosts(httpCtx, ctx, user, "feeds")),
339 }
340 }