fix(files): update all callers to provide seekable readers for S3 uploads

Update all code paths that pass file content to the storage layer to
provide io.ReadSeeker instead of io.Reader:

- Avatar upload: use bytes.NewReader instead of bytes.Buffer
- Background upload handler: use bytes.NewReader instead of bytes.Buffer
- Unsplash background: buffer response body into bytes.NewReader
- Dump restore: buffer zip entry into bytes.NewReader
- Migration structure: pass bytes.NewReader directly instead of wrapping
  in io.NopCloser
- Task attachment: change NewAttachment parameter from io.ReadCloser to
  io.ReadSeeker
This commit is contained in:
kolaente
2026-02-01 12:37:51 +01:00
parent 2a10d6a647
commit d8278f7d33
6 changed files with 19 additions and 11 deletions

View File

@@ -58,8 +58,7 @@ func (*TaskAttachment) TableName() string {
}
// NewAttachment creates a new task attachment
// Note: I'm not sure if only accepting an io.ReadCloser and not an afero.File or os.File instead is a good way of doing things.
func (ta *TaskAttachment) NewAttachment(s *xorm.Session, f io.ReadCloser, realname string, realsize uint64, a web.Auth) error {
func (ta *TaskAttachment) NewAttachment(s *xorm.Session, f io.ReadSeeker, realname string, realsize uint64, a web.Auth) error {
// Store the file
file, err := files.Create(f, realname, realsize, a)

View File

@@ -171,7 +171,7 @@ func StoreAvatarFile(s *xorm.Session, u *user.User, src io.Reader) (err error) {
}
// Save the file
f, err := files.CreateWithMime(buf, "avatar.png", uint64(buf.Len()), u, "image/png")
f, err := files.CreateWithMime(bytes.NewReader(buf.Bytes()), "avatar.png", uint64(buf.Len()), u, "image/png")
if err != nil {
return err
}

View File

@@ -278,7 +278,7 @@ func SaveBackgroundFile(s *xorm.Session, auth web.Auth, project *models.Project,
return err
}
f, err := files.Create(&buf, filename, filesize, auth)
f, err := files.Create(bytes.NewReader(buf.Bytes()), filename, filesize, auth)
if err != nil {
return err
}

View File

@@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
@@ -279,8 +280,14 @@ func (p *Provider) Set(s *xorm.Session, image *background.Image, project *models
}
log.Debugf("Pinged unsplash download endpoint for photo %s", image.ID)
// Buffer the response body so we have a seekable reader for S3 uploads
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
// Save it as a file in vikunja
file, err := files.Create(resp.Body, "", 0, auth)
file, err := files.Create(bytes.NewReader(bodyBytes), "", uint64(len(bodyBytes)), auth)
if err != nil {
return
}

View File

@@ -181,11 +181,15 @@ func Restore(filename string, overrideConfig bool) error {
return fmt.Errorf("could not open file %s: %w", i, err)
}
if err := f.Save(fc); err != nil {
return fmt.Errorf("could not save file: %w", err)
content, err := io.ReadAll(fc)
_ = fc.Close()
if err != nil {
return fmt.Errorf("could not read file %s: %w", i, err)
}
_ = fc.Close()
if err := f.Save(bytes.NewReader(content)); err != nil {
return fmt.Errorf("could not save file: %w", err)
}
log.Infof("Restored file %s", i)
}
log.Infof("Restored %d files.", len(filesFiles))

View File

@@ -18,7 +18,6 @@ package migration
import (
"bytes"
"io"
"xorm.io/xorm"
@@ -395,8 +394,7 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
oldID := a.ID
a.ID = 0
a.TaskID = t.ID
fr := io.NopCloser(bytes.NewReader(a.File.FileContent))
err = a.NewAttachment(s, fr, a.File.Name, a.File.Size, user)
err = a.NewAttachment(s, bytes.NewReader(a.File.FileContent), a.File.Name, a.File.Size, user)
if err != nil {
if models.IsErrTaskAttachmentIsTooLarge(err) {
log.Warningf("[creating structure] Attachment %s is too large (%d bytes), skipping: %v", a.File.Name, a.File.Size, err)