pico
created pr with
69.1
added 69.2
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 -3checkout any patchset in a patch request:
ssh pr.pico.sh print 69.[rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 69
Patchset
69.2
refactor(prose): use fs adapter for images
Eric Bower
2025-06-08T14:58:02ZSemantic diff summary
0 added,
4 modified,
1 signature changed,
0 removed
across 3 analyzed files
(1 file skipped: unsupported file type)
+1
-3
docker-compose.override.yml
#
+28
-34
pkg/apps/pgs/web.go
#
| ... | ... | @@ -439,7 +439,7 @@ func (web *WebRouter) ImageRequest(perm func(proj *db.Project) bool) http.Handle | |
| 439 | 439 | } | |
| 440 | 440 | } | |
| 441 | 441 | ||
| 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) { | |
| 443 | 443 | subdomain := shared.GetSubdomain(r) | |
| 444 | 444 | ||
| 445 | 445 | logger := web.Cfg.Logger.With( |
| ... | ... | @@ -459,6 +459,11 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro | |
| 459 | 459 | return | |
| 460 | 460 | } | |
| 461 | 461 | ||
| 462 | + | // override for requests from prose | |
| 463 | + | if fromProse { | |
| 464 | + | props.ProjectName = "prose" | |
| 465 | + | } | |
| 466 | + | ||
| 462 | 467 | logger = logger.With( | |
| 463 | 468 | "project", props.ProjectName, | |
| 464 | 469 | "user", props.Username, |
| ... | ... | @@ -475,41 +480,30 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro | |
| 475 | 480 | "userId", user.ID, | |
| 476 | 481 | ) | |
| 477 | 482 | ||
| 478 | - | projectID := "" | |
| 479 | - | // TODO: this could probably be cleaned up more | |
| 480 | - | // imgs wont have a project directory | |
| 481 | - | projectDir := "" | |
| 482 | 483 | 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 | + | } | |
| 494 | 491 | ||
| 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 | + | ) | |
| 499 | 496 | ||
| 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 | + | } | |
| 505 | 502 | ||
| 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 | |
| 513 | 507 | } | |
| 514 | 508 | ||
| 515 | 509 | if err != nil { |
| ... | ... | @@ -533,11 +527,11 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro | |
| 533 | 527 | Username: props.Username, | |
| 534 | 528 | UserID: user.ID, | |
| 535 | 529 | Subdomain: subdomain, | |
| 536 | - | ProjectDir: projectDir, | |
| 530 | + | ProjectID: project.ID, | |
| 531 | + | ProjectDir: project.ProjectDir, | |
| 537 | 532 | Filepath: fname, | |
| 538 | 533 | Bucket: bucket, | |
| 539 | 534 | ImgProcessOpts: opts, | |
| 540 | - | ProjectID: projectID, | |
| 541 | 535 | HasPicoPlus: hasPicoPlus, | |
| 542 | 536 | } | |
| 543 | 537 |
+59
-12
pkg/apps/prose/api.go
#
| ... | ... | @@ -865,6 +866,7 @@ func createMainRoutes(staticRoutes []shared.Route) []shared.Route { | |
| 865 | 866 | ||
| 866 | 867 | func imgRequest(w http.ResponseWriter, r *http.Request) { | |
| 867 | 868 | logger := shared.GetLogger(r) | |
| 869 | + | st := shared.GetStorage(r) | |
| 868 | 870 | dbpool := shared.GetDB(r) | |
| 869 | 871 | username := shared.GetUsernameFromRequest(r) | |
| 870 | 872 | user, err := dbpool.FindUserByName(username) |
| ... | ... | @@ -875,22 +877,67 @@ func imgRequest(w http.ResponseWriter, r *http.Request) { | |
| 875 | 877 | } | |
| 876 | 878 | logger = shared.LoggerWithUser(logger, user) | |
| 877 | 879 | ||
| 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)) | |
| 879 | 893 | 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) | |
| 882 | 896 | return | |
| 883 | 897 | } | |
| 884 | - | logger.Info("proxy image request", "url", destUrl.String()) | |
| 885 | 898 | ||
| 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) | |
| 892 | 940 | } | |
| 893 | - | proxy.ServeHTTP(w, r) | |
| 894 | 941 | } | |
| 895 | 942 | ||
| 896 | 943 | func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route { |