repos / pico

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

commit
2f0f0ee
parent
31dbc6e
author
Eric Bower
date
2024-03-01 02:15:16 +0000 UTC
refactor(shared): http router
9 files changed,  +154, -144
M go.mod
M go.sum
M feeds/api.go
+7, -5
 1@@ -3,9 +3,7 @@ package feeds
 2 import (
 3 	"fmt"
 4 	"net/http"
 5-	"time"
 6 
 7-	gocache "github.com/patrickmn/go-cache"
 8 	"github.com/picosh/pico/db/postgres"
 9 	"github.com/picosh/pico/shared"
10 	"github.com/picosh/pico/shared/storage"
11@@ -50,8 +48,6 @@ func StartApiServer() {
12 		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
13 	}
14 
15-	cache := gocache.New(2*time.Minute, 5*time.Minute)
16-
17 	if err != nil {
18 		logger.Error(err.Error())
19 	}
20@@ -68,7 +64,13 @@ func StartApiServer() {
21 
22 	mainRoutes := createMainRoutes(staticRoutes)
23 
24-	handler := shared.CreateServe(mainRoutes, []shared.Route{}, cfg, db, st, logger, cache)
25+	httpCtx := &shared.HttpCtx{
26+		Cfg:     cfg,
27+		Dbpool:  db,
28+		Storage: st,
29+		Logger:  logger,
30+	}
31+	handler := shared.CreateServe(mainRoutes, []shared.Route{}, httpCtx)
32 	router := http.HandlerFunc(handler)
33 
34 	portStr := fmt.Sprintf(":%s", cfg.Port)
M go.mod
+0, -1
1@@ -28,7 +28,6 @@ require (
2 	github.com/muesli/reflow v0.3.0
3 	github.com/muesli/termenv v0.15.2
4 	github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577
5-	github.com/patrickmn/go-cache v2.1.0+incompatible
6 	github.com/picosh/pobj v0.0.0-20240218150308-1dc70e819bbf
7 	github.com/picosh/ptun v0.0.0-20240225010823-a5e18b5be928
8 	github.com/picosh/send v0.0.0-20240217194807-77b972121e63
M go.sum
+0, -2
1@@ -183,8 +183,6 @@ github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577 h1:hVmVNt
2 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577/go.mod h1:G3Cu1AW+dmRLDFpOi8eUAfc3cGoRHUjTkGjeRcndgl4=
3 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e h1:76Dng5ms0fR+26doKZAvNqhi2UPfnLxGfPIDEr+BBlM=
4 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e/go.mod h1:nZBDA7+RD63GDJwjZmxhxac65MJqiCIHUUUvdYOsFkk=
5-github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
6-github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
7 github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
8 github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
9 github.com/picosh/pobj v0.0.0-20240218150308-1dc70e819bbf h1:xtr5DoSgoOqbPFnm8p5YjTqrRKVW8KYI0bDwwRiG8yI=
M imgs/api.go
+7, -7
 1@@ -12,7 +12,6 @@ import (
 2 	_ "net/http/pprof"
 3 
 4 	"github.com/gorilla/feeds"
 5-	gocache "github.com/patrickmn/go-cache"
 6 	"github.com/picosh/pico/db"
 7 	"github.com/picosh/pico/db/postgres"
 8 	"github.com/picosh/pico/pgs"
 9@@ -311,11 +310,6 @@ func StartApiServer() {
10 		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
11 	}
12 
13-	// cache resizing images since they are CPU-bound
14-	// we want to clear the cache since we are storing images
15-	// as []byte in-memory
16-	cache := gocache.New(2*time.Minute, 5*time.Minute)
17-
18 	if err != nil {
19 		logger.Error(err.Error())
20 	}
21@@ -328,7 +322,13 @@ func StartApiServer() {
22 	mainRoutes := createMainRoutes(staticRoutes)
23 	subdomainRoutes := createSubdomainRoutes(staticRoutes)
24 
25-	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger, cache)
26+	httpCtx := &shared.HttpCtx{
27+		Cfg:     cfg,
28+		Dbpool:  db,
29+		Storage: st,
30+		Logger:  logger,
31+	}
32+	handler := shared.CreateServe(mainRoutes, subdomainRoutes, httpCtx)
33 	router := http.HandlerFunc(handler)
34 
35 	portStr := fmt.Sprintf(":%s", cfg.Port)
M pastes/api.go
+7, -4
 1@@ -8,7 +8,6 @@ import (
 2 	"os"
 3 	"time"
 4 
 5-	gocache "github.com/patrickmn/go-cache"
 6 	"github.com/picosh/pico/db"
 7 	"github.com/picosh/pico/db/postgres"
 8 	"github.com/picosh/pico/shared"
 9@@ -364,8 +363,6 @@ func StartApiServer() {
10 		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
11 	}
12 
13-	cache := gocache.New(2*time.Minute, 5*time.Minute)
14-
15 	if err != nil {
16 		logger.Error(err.Error())
17 		return
18@@ -382,7 +379,13 @@ func StartApiServer() {
19 	mainRoutes := createMainRoutes(staticRoutes)
20 	subdomainRoutes := createSubdomainRoutes(staticRoutes)
21 
22-	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger, cache)
23+	httpCtx := &shared.HttpCtx{
24+		Cfg:     cfg,
25+		Dbpool:  db,
26+		Storage: st,
27+		Logger:  logger,
28+	}
29+	handler := shared.CreateServe(mainRoutes, subdomainRoutes, httpCtx)
30 	router := http.HandlerFunc(handler)
31 
32 	portStr := fmt.Sprintf(":%s", cfg.Port)
M pgs/api.go
+7, -10
 1@@ -15,7 +15,6 @@ import (
 2 	_ "net/http/pprof"
 3 
 4 	"github.com/gorilla/feeds"
 5-	gocache "github.com/patrickmn/go-cache"
 6 	"github.com/picosh/pico/db"
 7 	"github.com/picosh/pico/db/postgres"
 8 	"github.com/picosh/pico/shared"
 9@@ -33,7 +32,6 @@ type AssetHandler struct {
10 	Dbpool         db.DB
11 	Storage        storage.StorageServe
12 	Logger         *slog.Logger
13-	Cache          *gocache.Cache
14 	UserID         string
15 	Bucket         sst.Bucket
16 	ImgProcessOpts *storage.ImgProcessOpts
17@@ -352,7 +350,6 @@ func ServeAsset(fname string, opts *storage.ImgProcessOpts, fromImgs bool, hasPe
18 	dbpool := shared.GetDB(r)
19 	st := shared.GetStorage(r)
20 	logger := shared.GetLogger(r)
21-	cache := shared.GetCache(r)
22 
23 	props, err := getProjectFromSubdomain(subdomain)
24 	if err != nil {
25@@ -410,7 +407,6 @@ func ServeAsset(fname string, opts *storage.ImgProcessOpts, fromImgs bool, hasPe
26 		Dbpool:         dbpool,
27 		Storage:        st,
28 		Logger:         logger,
29-		Cache:          cache,
30 		Bucket:         bucket,
31 		ImgProcessOpts: opts,
32 	}
33@@ -487,17 +483,18 @@ func StartApiServer() {
34 		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
35 	}
36 
37-	// cache resizing images since they are CPU-bound
38-	// we want to clear the cache since we are storing images
39-	// as []byte in-memory
40-	cache := gocache.New(2*time.Minute, 5*time.Minute)
41-
42 	if err != nil {
43 		logger.Error(err.Error())
44 		return
45 	}
46 
47-	handler := shared.CreateServe(mainRoutes, createSubdomainRoutes(publicPerm), cfg, db, st, logger, cache)
48+	httpCtx := &shared.HttpCtx{
49+		Cfg:     cfg,
50+		Dbpool:  db,
51+		Storage: st,
52+		Logger:  logger,
53+	}
54+	handler := shared.CreateServe(mainRoutes, createSubdomainRoutes(publicPerm), httpCtx)
55 	router := http.HandlerFunc(handler)
56 
57 	portStr := fmt.Sprintf(":%s", cfg.Port)
M pgs/ssh.go
+97, -92
  1@@ -14,7 +14,6 @@ import (
  2 	"github.com/charmbracelet/ssh"
  3 	"github.com/charmbracelet/wish"
  4 	bm "github.com/charmbracelet/wish/bubbletea"
  5-	gocache "github.com/patrickmn/go-cache"
  6 	"github.com/picosh/pico/db"
  7 	"github.com/picosh/pico/db/postgres"
  8 	uploadassets "github.com/picosh/pico/filehandlers/assets"
  9@@ -89,6 +88,95 @@ type PicoApi struct {
 10 	PublicKey string `json:"public_key"`
 11 }
 12 
 13+type CtxHttpBridge = func(ssh.Context) http.Handler
 14+
 15+func createHttpHandler(httpCtx *shared.HttpCtx) CtxHttpBridge {
 16+	return func(ctx ssh.Context) http.Handler {
 17+		subdomain := ctx.User()
 18+		dbh := httpCtx.Dbpool
 19+		logger := httpCtx.Logger
 20+		log := logger.With(
 21+			"subdomain", subdomain,
 22+		)
 23+
 24+		pubkey, err := getPublicKeyCtx(ctx)
 25+		if err != nil {
 26+			log.Error(err.Error(), "subdomain", subdomain)
 27+			return http.HandlerFunc(unauthorizedHandler)
 28+		}
 29+		pubkeyStr, err := shared.KeyForKeyText(pubkey)
 30+		if err != nil {
 31+			log.Error(err.Error())
 32+			return http.HandlerFunc(unauthorizedHandler)
 33+		}
 34+		log = log.With(
 35+			"pubkey", pubkeyStr,
 36+		)
 37+
 38+		props, err := getProjectFromSubdomain(subdomain)
 39+		if err != nil {
 40+			log.Error(err.Error())
 41+			return http.HandlerFunc(unauthorizedHandler)
 42+		}
 43+
 44+		owner, err := dbh.FindUserForName(props.Username)
 45+		if err != nil {
 46+			log.Error(err.Error())
 47+			return http.HandlerFunc(unauthorizedHandler)
 48+		}
 49+		log = log.With(
 50+			"owner", owner.Name,
 51+		)
 52+
 53+		project, err := dbh.FindProjectByName(owner.ID, props.ProjectName)
 54+		if err != nil {
 55+			log.Error(err.Error())
 56+			return http.HandlerFunc(unauthorizedHandler)
 57+		}
 58+
 59+		requester, _ := dbh.FindUserForKey("", pubkeyStr)
 60+		if requester != nil {
 61+			log = logger.With(
 62+				"requester", requester.Name,
 63+			)
 64+		}
 65+
 66+		if !HasProjectAccess(project, owner, requester, pubkey) {
 67+			log.Error("no access")
 68+			return http.HandlerFunc(unauthorizedHandler)
 69+		}
 70+
 71+		log.Info("user has access to site")
 72+
 73+		routes := []shared.Route{
 74+			// special API endpoint for tunnel users accessing site
 75+			shared.NewRoute("GET", "/pico", func(w http.ResponseWriter, r *http.Request) {
 76+				w.Header().Set("Content-Type", "application/json")
 77+				pico := &PicoApi{
 78+					UserID:    "",
 79+					UserName:  "",
 80+					PublicKey: pubkeyStr,
 81+				}
 82+				if requester != nil {
 83+					pico.UserID = requester.ID
 84+					pico.UserName = requester.Name
 85+				}
 86+				err := json.NewEncoder(w).Encode(pico)
 87+				if err != nil {
 88+					log.Error(err.Error())
 89+				}
 90+			}),
 91+		}
 92+
 93+		subdomainRoutes := createSubdomainRoutes(allowPerm)
 94+		routes = append(routes, subdomainRoutes...)
 95+		finctx := httpCtx.CreateCtx(ctx, subdomain)
 96+		httpHandler := shared.CreateServeBasic(routes, finctx)
 97+		httpRouter := http.HandlerFunc(httpHandler)
 98+		return httpRouter
 99+	}
100+}
101+
102 func StartSshServer() {
103 	host := shared.GetEnv("PGS_HOST", "0.0.0.0")
104 	port := shared.GetEnv("PGS_SSH_PORT", "2222")
105@@ -116,99 +204,16 @@ func StartSshServer() {
106 		cfg,
107 		st,
108 	)
109-	cache := gocache.New(2*time.Minute, 5*time.Minute)
110 
111-	webTunnel := &ptun.WebTunnelHandler{
112-		Logger: logger,
113-		HttpHandler: func(ctx ssh.Context) http.Handler {
114-			subdomain := ctx.User()
115-			log := logger.With(
116-				"subdomain", subdomain,
117-			)
118-
119-			pubkey, err := getPublicKeyCtx(ctx)
120-			if err != nil {
121-				log.Error(err.Error(), "subdomain", subdomain)
122-				return http.HandlerFunc(unauthorizedHandler)
123-			}
124-			pubkeyStr, err := shared.KeyForKeyText(pubkey)
125-			if err != nil {
126-				log.Error(err.Error())
127-				return http.HandlerFunc(unauthorizedHandler)
128-			}
129-			log = log.With(
130-				"pubkey", pubkeyStr,
131-			)
132-
133-			props, err := getProjectFromSubdomain(subdomain)
134-			if err != nil {
135-				log.Error(err.Error())
136-				return http.HandlerFunc(unauthorizedHandler)
137-			}
138-
139-			owner, err := dbh.FindUserForName(props.Username)
140-			if err != nil {
141-				log.Error(err.Error())
142-				return http.HandlerFunc(unauthorizedHandler)
143-			}
144-			log = log.With(
145-				"owner", owner.Name,
146-			)
147+	httpCtx := &shared.HttpCtx{
148+		Cfg:     cfg,
149+		Dbpool:  dbh,
150+		Storage: st,
151+	}
152 
153-			project, err := dbh.FindProjectByName(owner.ID, props.ProjectName)
154-			if err != nil {
155-				log.Error(err.Error())
156-				return http.HandlerFunc(unauthorizedHandler)
157-			}
158-
159-			requester, _ := dbh.FindUserForKey("", pubkeyStr)
160-			if requester != nil {
161-				log = logger.With(
162-					"requester", requester.Name,
163-				)
164-			}
165-
166-			if !HasProjectAccess(project, owner, requester, pubkey) {
167-				log.Error("no access")
168-				return http.HandlerFunc(unauthorizedHandler)
169-			}
170-
171-			log.Info("user has access to site")
172-
173-			routes := []shared.Route{
174-				// special API endpoint for tunnel users accessing site
175-				shared.NewRoute("GET", "/pico", func(w http.ResponseWriter, r *http.Request) {
176-					w.Header().Set("Content-Type", "application/json")
177-					pico := &PicoApi{
178-						UserID:    "",
179-						UserName:  "",
180-						PublicKey: pubkeyStr,
181-					}
182-					if requester != nil {
183-						pico.UserID = requester.ID
184-						pico.UserName = requester.Name
185-					}
186-					err := json.NewEncoder(w).Encode(pico)
187-					if err != nil {
188-						log.Error(err.Error())
189-					}
190-				}),
191-			}
192-
193-			subdomainRoutes := createSubdomainRoutes(allowPerm)
194-			routes = append(routes, subdomainRoutes...)
195-			httpHandler := shared.CreateServeBasic(
196-				routes,
197-				subdomain,
198-				cfg,
199-				dbh,
200-				st,
201-				logger,
202-				cache,
203-			)
204-			httpRouter := http.HandlerFunc(httpHandler)
205-			return httpRouter
206-		},
207+	webTunnel := &ptun.WebTunnelHandler{
208+		Logger:      logger,
209+		HttpHandler: createHttpHandler(httpCtx),
210 	}
211 
212 	s, err := wish.NewServer(
M prose/api.go
+7, -4
 1@@ -14,7 +14,6 @@ import (
 2 	"slices"
 3 
 4 	"github.com/gorilla/feeds"
 5-	gocache "github.com/patrickmn/go-cache"
 6 	"github.com/picosh/pico/db"
 7 	"github.com/picosh/pico/db/postgres"
 8 	"github.com/picosh/pico/imgs"
 9@@ -879,8 +878,6 @@ func StartApiServer() {
10 		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
11 	}
12 
13-	cache := gocache.New(2*time.Minute, 5*time.Minute)
14-
15 	if err != nil {
16 		logger.Error(err.Error())
17 	}
18@@ -894,7 +891,13 @@ func StartApiServer() {
19 	mainRoutes := createMainRoutes(staticRoutes)
20 	subdomainRoutes := createSubdomainRoutes(staticRoutes)
21 
22-	handler := shared.CreateServe(mainRoutes, subdomainRoutes, cfg, db, st, logger, cache)
23+	httpCtx := &shared.HttpCtx{
24+		Cfg:     cfg,
25+		Dbpool:  db,
26+		Storage: st,
27+		Logger:  logger,
28+	}
29+	handler := shared.CreateServe(mainRoutes, subdomainRoutes, httpCtx)
30 	router := http.HandlerFunc(handler)
31 
32 	portStr := fmt.Sprintf(":%s", cfg.Port)
M shared/router.go
+22, -19
 1@@ -10,7 +10,6 @@ import (
 2 	"regexp"
 3 	"strings"
 4 
 5-	"github.com/patrickmn/go-cache"
 6 	"github.com/picosh/pico/db"
 7 	"github.com/picosh/pico/shared/storage"
 8 )
 9@@ -45,17 +44,25 @@ func CreatePProfRoutes(routes []Route) []Route {
10 }
11 
12 type ServeFn func(http.ResponseWriter, *http.Request)
13+type HttpCtx struct {
14+	Logger  *slog.Logger
15+	Cfg     *ConfigSite
16+	Dbpool  db.DB
17+	Storage storage.StorageServe
18+}
19+
20+func (hc *HttpCtx) CreateCtx(prevCtx context.Context, subdomain string) context.Context {
21+	loggerCtx := context.WithValue(prevCtx, ctxLoggerKey{}, hc.Logger)
22+	subdomainCtx := context.WithValue(loggerCtx, ctxSubdomainKey{}, subdomain)
23+	dbCtx := context.WithValue(subdomainCtx, ctxDBKey{}, hc.Dbpool)
24+	storageCtx := context.WithValue(dbCtx, ctxStorageKey{}, hc.Storage)
25+	cfgCtx := context.WithValue(storageCtx, ctxCfg{}, hc.Cfg)
26+	return cfgCtx
27+}
28 
29-func CreateServeBasic(routes []Route, subdomain string, cfg *ConfigSite, dbpool db.DB, st storage.StorageServe, logger *slog.Logger, cache *cache.Cache) ServeFn {
30+func CreateServeBasic(routes []Route, ctx context.Context) ServeFn {
31 	return func(w http.ResponseWriter, r *http.Request) {
32 		var allow []string
33-		loggerCtx := context.WithValue(r.Context(), ctxLoggerKey{}, logger)
34-		subdomainCtx := context.WithValue(loggerCtx, ctxSubdomainKey{}, subdomain)
35-		dbCtx := context.WithValue(subdomainCtx, ctxDBKey{}, dbpool)
36-		storageCtx := context.WithValue(dbCtx, ctxStorageKey{}, st)
37-		cfgCtx := context.WithValue(storageCtx, ctxCfg{}, cfg)
38-		cacheCtx := context.WithValue(cfgCtx, ctxCacheKey{}, cache)
39-
40 		for _, route := range routes {
41 			matches := route.Regex.FindStringSubmatch(r.URL.Path)
42 			if len(matches) > 0 {
43@@ -63,8 +70,8 @@ func CreateServeBasic(routes []Route, subdomain string, cfg *ConfigSite, dbpool
44 					allow = append(allow, route.Method)
45 					continue
46 				}
47-				ctx := context.WithValue(cacheCtx, ctxKey{}, matches[1:])
48-				route.Handler(w, r.WithContext(ctx))
49+				finctx := context.WithValue(ctx, ctxKey{}, matches[1:])
50+				route.Handler(w, r.WithContext(finctx))
51 				return
52 			}
53 		}
54@@ -103,10 +110,11 @@ func findRouteConfig(r *http.Request, routes []Route, subdomainRoutes []Route, c
55 	return curRoutes, subdomain
56 }
57 
58-func CreateServe(routes []Route, subdomainRoutes []Route, cfg *ConfigSite, dbpool db.DB, st storage.StorageServe, logger *slog.Logger, cache *cache.Cache) ServeFn {
59+func CreateServe(routes []Route, subdomainRoutes []Route, httpCtx *HttpCtx) ServeFn {
60 	return func(w http.ResponseWriter, r *http.Request) {
61-		curRoutes, subdomain := findRouteConfig(r, routes, subdomainRoutes, cfg)
62-		router := CreateServeBasic(curRoutes, subdomain, cfg, dbpool, st, logger, cache)
63+		curRoutes, subdomain := findRouteConfig(r, routes, subdomainRoutes, httpCtx.Cfg)
64+		ctx := httpCtx.CreateCtx(r.Context(), subdomain)
65+		router := CreateServeBasic(curRoutes, ctx)
66 		router(w, r)
67 	}
68 }
69@@ -115,7 +123,6 @@ type ctxDBKey struct{}
70 type ctxStorageKey struct{}
71 type ctxKey struct{}
72 type ctxLoggerKey struct{}
73-type ctxCacheKey struct{}
74 type ctxSubdomainKey struct{}
75 type ctxCfg struct{}
76 
77@@ -127,10 +134,6 @@ func GetLogger(r *http.Request) *slog.Logger {
78 	return r.Context().Value(ctxLoggerKey{}).(*slog.Logger)
79 }
80 
81-func GetCache(r *http.Request) *cache.Cache {
82-	return r.Context().Value(ctxCacheKey{}).(*cache.Cache)
83-}
84-
85 func GetDB(r *http.Request) db.DB {
86 	return r.Context().Value(ctxDBKey{}).(db.DB)
87 }