pico

created pr with 69.1 on 2025-06-08T14:58:33Z · by c8ef7d19
added 69.2 on 2025-06-08T15:26:57Z · by c8ef7d19
1: 827f272 = 1: 827f272 feat(storage): base storage fn
2: e63282c = 2: e63282c feat(prose): use storage base for prose
3: 2a52510 = 3: 2a52510 refactor(prose): use fs adapter for images
-: ------- > 4: 6ddc654 refactor: proxy headers for images
cmds
checkout latest patchset:
ssh pr.pico.sh print 69 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 69.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 69
+1 -3 docker-compose.override.yml #
......@@ -15,11 +15,9 @@ services:
1515 env_file:
1616 - .env.example
1717 volumes:
18- - ./data/storage/data:/storage
18+ - /tmp/pico_storage:/storage
1919 ports:
2020 - "8080:8080"
21- links:
22- - minio
2321 pastes-web:
2422 build:
2523 args:
+28 -34 pkg/apps/pgs/web.go #
......@@ -439,7 +439,7 @@ func (web *WebRouter) ImageRequest(perm func(proj *db.Project) bool) http.Handle
439439 }
440440 }
441441
442-func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fromImgs bool, hasPerm HasPerm, w http.ResponseWriter, r *http.Request) {
442+func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fromProse bool, hasPerm HasPerm, w http.ResponseWriter, r *http.Request) {
443443 subdomain := shared.GetSubdomain(r)
444444
445445 logger := web.Cfg.Logger.With(
......@@ -459,6 +459,11 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro
459459 return
460460 }
461461
462+ // override for requests from prose
463+ if fromProse {
464+ props.ProjectName = "prose"
465+ }
466+
462467 logger = logger.With(
463468 "project", props.ProjectName,
464469 "user", props.Username,
......@@ -475,41 +480,30 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro
475480 "userId", user.ID,
476481 )
477482
478- projectID := ""
479- // TODO: this could probably be cleaned up more
480- // imgs wont have a project directory
481- projectDir := ""
482483 var bucket sst.Bucket
483- // imgs has a different bucket directory
484- if fromImgs {
485- bucket, err = web.Cfg.Storage.GetBucket(shared.GetImgsBucketName(user.ID))
486- } else {
487- bucket, err = web.Cfg.Storage.GetBucket(shared.GetAssetBucketName(user.ID))
488- project, perr := web.Cfg.DB.FindProjectByName(user.ID, props.ProjectName)
489- if perr != nil {
490- logger.Info("project not found")
491- http.Error(w, "project not found", http.StatusNotFound)
492- return
493- }
484+ bucket, err = web.Cfg.Storage.GetBucket(shared.GetAssetBucketName(user.ID))
485+ project, perr := web.Cfg.DB.FindProjectByName(user.ID, props.ProjectName)
486+ if perr != nil {
487+ logger.Info("project not found")
488+ http.Error(w, "project not found", http.StatusNotFound)
489+ return
490+ }
494491
495- logger = logger.With(
496- "projectId", project.ID,
497- "project", project.Name,
498- )
492+ logger = logger.With(
493+ "projectId", project.ID,
494+ "project", project.Name,
495+ )
499496
500- if project.Blocked != "" {
501- logger.Error("project has been blocked")
502- http.Error(w, project.Blocked, http.StatusForbidden)
503- return
504- }
497+ if project.Blocked != "" {
498+ logger.Error("project has been blocked")
499+ http.Error(w, project.Blocked, http.StatusForbidden)
500+ return
501+ }
505502
506- projectID = project.ID
507- projectDir = project.ProjectDir
508- if !hasPerm(project) {
509- logger.Error("You do not have access to this site")
510- http.Error(w, "You do not have access to this site", http.StatusUnauthorized)
511- return
512- }
503+ if !hasPerm(project) {
504+ logger.Error("You do not have access to this site")
505+ http.Error(w, "You do not have access to this site", http.StatusUnauthorized)
506+ return
513507 }
514508
515509 if err != nil {
......@@ -533,11 +527,11 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro
533527 Username: props.Username,
534528 UserID: user.ID,
535529 Subdomain: subdomain,
536- ProjectDir: projectDir,
530+ ProjectID: project.ID,
531+ ProjectDir: project.ProjectDir,
537532 Filepath: fname,
538533 Bucket: bucket,
539534 ImgProcessOpts: opts,
540- ProjectID: projectID,
541535 HasPicoPlus: hasPicoPlus,
542536 }
543537
+59 -12 pkg/apps/prose/api.go #
......@@ -4,10 +4,11 @@ import (
44 "bytes"
55 "fmt"
66 "html/template"
7+ "io"
78 "net/http"
8- "net/http/httputil"
99 "net/url"
1010 "os"
11+ "path/filepath"
1112 "strconv"
1213 "strings"
1314 "time"
......@@ -865,6 +866,7 @@ func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
865866
866867 func imgRequest(w http.ResponseWriter, r *http.Request) {
867868 logger := shared.GetLogger(r)
869+ st := shared.GetStorage(r)
868870 dbpool := shared.GetDB(r)
869871 username := shared.GetUsernameFromRequest(r)
870872 user, err := dbpool.FindUserByName(username)
......@@ -875,22 +877,67 @@ func imgRequest(w http.ResponseWriter, r *http.Request) {
875877 }
876878 logger = shared.LoggerWithUser(logger, user)
877879
878- destUrl, err := url.Parse(fmt.Sprintf("https://%s-prose.pgs.sh%s", username, r.URL.Path))
880+ rawname := shared.GetField(r, 0)
881+ imgOpts := shared.GetField(r, 1)
882+ fname := filepath.Join("/prose", rawname)
883+
884+ opts, err := storage.UriToImgProcessOpts(imgOpts)
885+ if err != nil {
886+ errMsg := fmt.Sprintf("error processing img options: %s", err.Error())
887+ logger.Error("error processing img options", "err", errMsg)
888+ http.Error(w, errMsg, http.StatusUnprocessableEntity)
889+ return
890+ }
891+
892+ bucket, err := st.GetBucket(shared.GetAssetBucketName(user.ID))
879893 if err != nil {
880- logger.Error("could not parse image proxy url", "username", username)
881- http.Error(w, "could not parse image proxy url", http.StatusInternalServerError)
894+ logger.Error("bucket", "err", err)
895+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
882896 return
883897 }
884- logger.Info("proxy image request", "url", destUrl.String())
885898
886- proxy := httputil.NewSingleHostReverseProxy(destUrl)
887- oldDirector := proxy.Director
888- proxy.Director = func(r *http.Request) {
889- oldDirector(r)
890- r.Host = destUrl.Host
891- r.URL = destUrl
899+ fmt.Println("HERERERERER", fname, opts)
900+ contents, info, err := st.ServeObject(bucket, fname, opts)
901+ if err != nil {
902+ logger.Error("serve object", "err", err)
903+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
904+ return
905+ }
906+
907+ contentType := ""
908+ if info != nil {
909+ contentType = info.Metadata.Get("content-type")
910+ if info.Size != 0 {
911+ w.Header().Add("content-length", strconv.Itoa(int(info.Size)))
912+ }
913+ if info.ETag != "" {
914+ // Minio SDK trims off the mandatory quotes (RFC 7232 ยง 2.3)
915+ w.Header().Add("etag", fmt.Sprintf("\"%s\"", info.ETag))
916+ }
917+
918+ if !info.LastModified.IsZero() {
919+ w.Header().Add("last-modified", info.LastModified.UTC().Format(http.TimeFormat))
920+ }
921+ }
922+
923+ if w.Header().Get("content-type") == "" {
924+ w.Header().Set("content-type", contentType)
925+ }
926+
927+ // Allows us to invalidate the cache when files are modified
928+ // w.Header().Set("surrogate-key", h.Subdomain)
929+
930+ finContentType := w.Header().Get("content-type")
931+ logger.Info(
932+ "serving asset",
933+ "asset", fname,
934+ "contentType", finContentType,
935+ )
936+
937+ _, err = io.Copy(w, contents)
938+ if err != nil {
939+ logger.Error("io copy", "err", err)
892940 }
893- proxy.ServeHTTP(w, r)
894941 }
895942
896943 func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
+1 -1 pkg/shared/storage/fs.go #
......@@ -39,7 +39,7 @@ func (s *StorageFS) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProces
3939 info.Metadata.Set("content-type", mimeType)
4040 } else {
4141 filePath := filepath.Join(bucket.Name, fpath)
42- dataURL := fmt.Sprintf("s3://%s", filePath)
42+ dataURL := fmt.Sprintf("local:///%s", filePath)
4343 rc, info, err = HandleProxy(s.Logger, dataURL, opts)
4444 }
4545 if err != nil {
Back to top