repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

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
M plus/routes.go
+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 }