From dd07ef9c8e7db926893a22a6224e90a71dda2945 Mon Sep 17 00:00:00 2001 From: Wilhelm Berggren Date: Sun, 26 Jul 2026 19:01:04 +0000 Subject: [PATCH] appview: upload images as blobs (pulls) Signed-off-by: Wilhelm Berggren Signed-off-by: Seongmin Lee --- appview/models/pull.go | 2 ++ appview/pulls/compose.go | 19 ++++++++++++++++--- appview/pulls/create.go | 20 ++++++++++++++------ appview/pulls/edit.go | 9 +++++++++ appview/pulls/resubmit.go | 2 +- 5 file(s) changed, 42 insertion(s)(+), 10 deletion(s)(-) diff --git a/appview/models/pull.go b/appview/models/pull.go --- a/appview/models/pull.go +++ b/appview/models/pull.go @@ -74,6 +74,7 @@ Submissions []*PullSubmission Mentions []syntax.DID References []syntax.ATURI + Blobs []*lexutil.LexBlob // stacking DependentOn *syntax.ATURI @@ -115,6 +116,7 @@ Mentions: mentions, References: references, CreatedAt: p.Created.Format(time.RFC3339), + Blobs: p.Blobs, Target: &tangled.RepoPull_Target{ Repo: string(p.RepoDid), Branch: p.TargetBranch, diff --git a/appview/pulls/compose.go b/appview/pulls/compose.go --- a/appview/pulls/compose.go +++ b/appview/pulls/compose.go @@ -130,6 +130,7 @@ stackTitles := parseBracketedForm(r.Form, "stackTitle") stackBodies := parseBracketedForm(r.Form, "stackBody") + stackBlobs := parseStackBlobForms(r.Form) // Handle the PR creation based on the type if isBranchBased { @@ -137,19 +138,19 @@ s.pages.Notice(w, "pull", "This knot doesn't support branch-based pull requests. Try another way?") return } - s.handleBranchBasedPull(w, r, f, userDid, title, body, targetBranch, sourceBranch, isStacked, stackTitles, stackBodies) + s.handleBranchBasedPull(w, r, f, userDid, title, body, targetBranch, sourceBranch, isStacked, stackTitles, stackBodies, stackBlobs) } else if isForkBased { if !caps.PullRequests.ForkSubmissions { s.pages.Notice(w, "pull", "This knot doesn't support fork-based pull requests. Try another way?") return } - s.handleForkBasedPull(w, r, f, userDid, fromFork, title, body, targetBranch, sourceBranch, isStacked, stackTitles, stackBodies) + s.handleForkBasedPull(w, r, f, userDid, fromFork, title, body, targetBranch, sourceBranch, isStacked, stackTitles, stackBodies, stackBlobs) } else if isPatchBased { if !caps.PullRequests.PatchSubmissions { s.pages.Notice(w, "pull", "This knot doesn't support patch-based pull requests. Send your patch over email.") return } - s.handlePatchBasedPull(w, r, f, userDid, title, body, targetBranch, patch, isStacked, stackTitles, stackBodies) + s.handlePatchBasedPull(w, r, f, userDid, title, body, targetBranch, patch, isStacked, stackTitles, stackBodies, stackBlobs) } return } @@ -506,6 +507,18 @@ continue } out[parts[0]] = vals[0] + } + return out +} + +func parseStackBlobForms(form url.Values) map[string][]string { + out := make(map[string][]string) + for key, vals := range form { + parts, ok := bracketComponents(key, "stackBlobs") + if !ok || len(parts) != 1 || parts[0] == "" || len(vals) == 0 { + continue + } + out[parts[0]] = vals } return out } diff --git a/appview/pulls/create.go b/appview/pulls/create.go --- a/appview/pulls/create.go +++ b/appview/pulls/create.go @@ -38,6 +38,7 @@ sourceBranch string, isStacked bool, stackTitles, stackBodies map[string]string, + stackBlobs map[string][]string, ) { l := s.logger.With("handler", "handleBranchBasedPull", "user", userDid, "target_branch", targetBranch, "source_branch", sourceBranch, "is_stacked", isStacked) @@ -81,20 +82,20 @@ Branch: sourceBranch, } - s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, combined, sourceRev, pullSource, isStacked, stackTitles, stackBodies) + s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, combined, sourceRev, pullSource, isStacked, stackTitles, stackBodies, stackBlobs) } -func (s *Pulls) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, userDid syntax.DID, title, body, targetBranch, patch string, isStacked bool, stackTitles, stackBodies map[string]string) { +func (s *Pulls) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, userDid syntax.DID, title, body, targetBranch, patch string, isStacked bool, stackTitles, stackBodies map[string]string, stackBlobs map[string][]string) { if err := validatePatch(&patch); err != nil { s.logger.Error("patch validation failed", "err", err) s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") return } - s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, "", "", nil, isStacked, stackTitles, stackBodies) + s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, "", "", nil, isStacked, stackTitles, stackBodies, stackBlobs) } -func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, userDid syntax.DID, forkRepoDid string, title, body, targetBranch, sourceBranch string, isStacked bool, stackTitles, stackBodies map[string]string) { +func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, repo *models.Repo, userDid syntax.DID, forkRepoDid string, title, body, targetBranch, sourceBranch string, isStacked bool, stackTitles, stackBodies map[string]string, stackBlobs map[string][]string) { l := s.logger.With("handler", "handleForkBasedPull", "user", userDid, "fork_repo_did", forkRepoDid, "target_branch", targetBranch, "source_branch", sourceBranch, "is_stacked", isStacked) if forkRepoDid == "" { @@ -190,7 +191,7 @@ RepoDid: &forkDid, } - s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, combined, sourceRev, pullSource, isStacked, stackTitles, stackBodies) + s.createPullRequest(w, r, repo, userDid, title, body, targetBranch, patch, combined, sourceRev, pullSource, isStacked, stackTitles, stackBodies, stackBlobs) } func (s *Pulls) createPullRequest( @@ -205,6 +206,7 @@ pullSource *models.PullSource, isStacked bool, stackTitles, stackBodies map[string]string, + stackBlobs map[string][]string, ) { l := s.logger.With("handler", "createPullRequest", "user", userDid, "target_branch", targetBranch, "is_stacked", isStacked) @@ -221,6 +223,7 @@ pullSource, stackTitles, stackBodies, + stackBlobs, ) return } @@ -298,6 +301,8 @@ Repo: repo, } + pull.Blobs = models.ParseBlobs(r.PostForm["blobs"], body) + record := pull.AsRecord() _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.RepoPullNSID, @@ -348,6 +353,7 @@ sourceRev string, pullSource *models.PullSource, stackTitles, stackBodies map[string]string, + stackBlobs map[string][]string, ) { l := s.logger.With("handler", "createStackedPullRequest", "user", userDid, "target_branch", targetBranch, "source_rev", sourceRev) @@ -388,7 +394,7 @@ } // build a stack out of this patch - stack, err := s.newStack(r.Context(), repo, userDid, targetBranch, pullSource, formatPatches, blobs, stackTitles, stackBodies) + stack, err := s.newStack(r.Context(), repo, userDid, targetBranch, pullSource, formatPatches, blobs, stackTitles, stackBodies, stackBlobs) if err != nil { l.Error("failed to create stack", "err", err) s.pages.Notice(w, "pull", fmt.Sprintf("Failed to create stack: %v", err)) @@ -464,6 +470,7 @@ formatPatches []types.FormatPatch, blobs []*lexutil.LexBlob, stackTitles, stackBodies map[string]string, + stackBlobs map[string][]string, ) (models.Stack, error) { var stack models.Stack var parentAtUri *syntax.ATURI @@ -513,6 +520,7 @@ DependentOn: parentAtUri, Repo: repo, } + pull.Blobs = models.ParseBlobs(stackBlobs[cid], body) stack = append(stack, &pull) diff --git a/appview/pulls/edit.go b/appview/pulls/edit.go --- a/appview/pulls/edit.go +++ b/appview/pulls/edit.go @@ -53,6 +53,15 @@ return } + // merge existing pins with new uploads, dropping any removed from body + var existingBlobs []*lexutil.LexBlob + if ex.Value != nil { + if prev, ok := ex.Value.Val.(*tangled.RepoPull); ok { + existingBlobs = prev.Blobs + } + } + newPull.Blobs = models.MergeBlobs(existingBlobs, r.PostForm["blobs"], newPull.Body) + newRecord := newPull.AsRecord() _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ Collection: tangled.RepoPullNSID, diff --git a/appview/pulls/resubmit.go b/appview/pulls/resubmit.go --- a/appview/pulls/resubmit.go +++ b/appview/pulls/resubmit.go @@ -407,7 +407,7 @@ blobs[i] = blob.Blob } - newStack, err := s.newStack(r.Context(), repo, userDid, targetBranch, pull.PullSource, formatPatches, blobs, nil, nil) + newStack, err := s.newStack(r.Context(), repo, userDid, targetBranch, pull.PullSource, formatPatches, blobs, nil, nil, nil) if err != nil { l.Error("failed to create resubmitted stack", "err", err) s.pages.Notice(w, "pull-resubmit-error", "Failed to resubmit pull request. Try again later.") -- tangled.sh