Have got uploads working
This commit is contained in:
parent
97112d99dd
commit
6b697e008f
20 changed files with 751 additions and 7 deletions
124
services/uploads/pending.go
Normal file
124
services/uploads/pending.go
Normal 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
|
||||
}
|
||||
38
services/uploads/services.go
Normal file
38
services/uploads/services.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue