Have got uploads working

This commit is contained in:
Leon Mika 2026-03-02 20:48:41 +11:00
parent 97112d99dd
commit 6b697e008f
20 changed files with 751 additions and 7 deletions

124
services/uploads/pending.go Normal file
View file

@ -0,0 +1,124 @@
package uploads
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"io"
"os"
"path/filepath"
"time"
"emperror.dev/errors"
"lmika.dev/lmika/weiro/models"
)
type NewPendingRequest struct {
FileSize int64 `json:"size"`
Filename string `json:"name"`
MIMEType string `json:"type"`
}
func (s *Service) NewPending(ctx context.Context, req NewPendingRequest) (models.PendingUpload, error) {
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return models.PendingUpload{}, err
}
pending := models.PendingUpload{
GUID: models.NewNanoID(),
SiteID: site.ID,
UserID: user.ID,
FileSize: req.FileSize,
Filename: req.Filename,
MIMEType: req.MIMEType,
UploadStarted: time.Now(),
}
if err := s.db.SavePendingUpload(ctx, &pending); err != nil {
return models.PendingUpload{}, err
}
if err := os.MkdirAll(s.pendingDir, 0755); err != nil {
return models.PendingUpload{}, err
}
pendingDataFile, err := os.Create(filepath.Join(s.pendingDir, pending.GUID+".upload"))
if err != nil {
return models.PendingUpload{}, err
}
return pending, pendingDataFile.Close()
}
func (s *Service) WriteToPending(ctx context.Context, pendingGUID string, data []byte) error {
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return err
}
pu, err := s.db.SelectPendingUploadByGUID(ctx, pendingGUID)
if err != nil {
return err
} else if pu.SiteID != site.ID || pu.UserID != user.ID {
return errors.New("invalid pending upload")
}
pendingDataFilename := filepath.Join(s.pendingDir, pu.GUID+".upload")
if _, err := os.Stat(pendingDataFilename); err != nil {
return err
}
pendingDataFile, err := os.OpenFile(pendingDataFilename, os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
defer pendingDataFile.Close()
pendingDataFile.Seek(0, io.SeekEnd)
if _, err := pendingDataFile.Write(data); err != nil {
return err
}
return nil
}
func (s *Service) FinalizePending(ctx context.Context, pendingGUID string, expectedHash string) error {
site, user, err := s.fetchSiteAndUser(ctx)
if err != nil {
return err
}
pu, err := s.db.SelectPendingUploadByGUID(ctx, pendingGUID)
if err != nil {
return err
} else if pu.SiteID != site.ID || pu.UserID != user.ID {
return errors.New("invalid pending upload")
}
expectedHashBytes, err := hex.DecodeString(expectedHash)
if err != nil {
return err
}
pendingDataFilename := filepath.Join(s.pendingDir, pu.GUID+".upload")
if _, err := os.Stat(pendingDataFilename); err != nil {
return err
}
pendingDataFile, err := os.Open(pendingDataFilename)
if err != nil {
return err
}
defer pendingDataFile.Close()
shaSum := sha256.New()
if _, err := io.Copy(shaSum, pendingDataFile); err != nil {
return err
}
if !bytes.Equal(shaSum.Sum(nil), expectedHashBytes) {
return errors.New("hash mismatch")
}
return nil
}

View file

@ -0,0 +1,38 @@
package uploads
import (
"context"
"lmika.dev/lmika/weiro/models"
"lmika.dev/lmika/weiro/providers/db"
)
type Service struct {
db *db.Provider
pendingDir string
}
func New(db *db.Provider, pendingDir string) *Service {
return &Service{
db: db,
pendingDir: pendingDir,
}
}
func (s *Service) fetchSiteAndUser(ctx context.Context) (models.Site, models.User, error) {
user, ok := models.GetUser(ctx)
if !ok {
return models.Site{}, models.User{}, models.UserRequiredError
}
site, ok := models.GetSite(ctx)
if !ok {
return models.Site{}, models.User{}, models.SiteRequiredError
}
if site.OwnerID != user.ID {
return models.Site{}, models.User{}, models.PermissionError
}
return site, user, nil
}