From 0a9af9cde8ec5e8c7935fe2aa564626851c0611b Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Mon, 2 Mar 2026 21:10:09 +1100 Subject: [PATCH] Started a repository of the uploads --- models/uploads.go | 15 ++++---- providers/db/gen/sqlgen/models.go | 1 + .../db/gen/sqlgen/pending_uploads.sql.go | 9 +++++ providers/db/gen/sqlgen/uploads.sql.go | 6 ++-- providers/db/uploads.go | 8 +++-- providers/uploadfiles/provider.go | 35 +++++++++++++++++++ services/services.go | 5 ++- services/uploads/pending.go | 26 +++++++++++++- services/uploads/services.go | 5 ++- sql/queries/pending_uploads.sql | 5 ++- sql/schema/02_upload.up.sql | 1 + 11 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 providers/uploadfiles/provider.go diff --git a/models/uploads.go b/models/uploads.go index 0e818c5..995de60 100644 --- a/models/uploads.go +++ b/models/uploads.go @@ -3,13 +3,14 @@ package models import "time" type Upload struct { - ID int64 `json:"id"` - SiteID int64 `json:"site_id"` - GUID string `json:"guid"` - MIMEType string `json:"mime_type"` - Filename string `json:"filename"` - CreatedAt int64 `json:"created_at"` - Alt string `json:"alt"` + ID int64 `json:"id"` + SiteID int64 `json:"site_id"` + GUID string `json:"guid"` + FileSize int64 `json:"file_size"` + MIMEType string `json:"mime_type"` + Filename string `json:"filename"` + CreatedAt time.Time `json:"created_at"` + Alt string `json:"alt"` } type PendingUpload struct { diff --git a/providers/db/gen/sqlgen/models.go b/providers/db/gen/sqlgen/models.go index 6aed257..5b22452 100644 --- a/providers/db/gen/sqlgen/models.go +++ b/providers/db/gen/sqlgen/models.go @@ -55,6 +55,7 @@ type Upload struct { Guid string MimeType string Filename string + FileSize int64 Alt string CreatedAt int64 } diff --git a/providers/db/gen/sqlgen/pending_uploads.sql.go b/providers/db/gen/sqlgen/pending_uploads.sql.go index c2ca66b..96d8c3b 100644 --- a/providers/db/gen/sqlgen/pending_uploads.sql.go +++ b/providers/db/gen/sqlgen/pending_uploads.sql.go @@ -9,6 +9,15 @@ import ( "context" ) +const deletePendingUpload = `-- name: DeletePendingUpload :exec +DELETE FROM pending_uploads WHERE guid = ? +` + +func (q *Queries) DeletePendingUpload(ctx context.Context, guid string) error { + _, err := q.db.ExecContext(ctx, deletePendingUpload, guid) + return err +} + const insertPendingUpload = `-- name: InsertPendingUpload :one INSERT INTO pending_uploads ( site_id, diff --git a/providers/db/gen/sqlgen/uploads.sql.go b/providers/db/gen/sqlgen/uploads.sql.go index 6891422..6173374 100644 --- a/providers/db/gen/sqlgen/uploads.sql.go +++ b/providers/db/gen/sqlgen/uploads.sql.go @@ -52,7 +52,7 @@ func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) erro } const selectUploadByID = `-- name: SelectUploadByID :one -SELECT id, site_id, guid, mime_type, filename, alt, created_at FROM uploads WHERE id = ? +SELECT id, site_id, guid, mime_type, filename, file_size, alt, created_at FROM uploads WHERE id = ? ` func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload, error) { @@ -64,6 +64,7 @@ func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload, &i.Guid, &i.MimeType, &i.Filename, + &i.FileSize, &i.Alt, &i.CreatedAt, ) @@ -71,7 +72,7 @@ func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload, } const selectUploadsOfSite = `-- name: SelectUploadsOfSite :many -SELECT id, site_id, guid, mime_type, filename, alt, created_at FROM uploads WHERE site_id = ? ORDER BY created_at DESC +SELECT id, site_id, guid, mime_type, filename, file_size, alt, created_at FROM uploads WHERE site_id = ? ORDER BY created_at DESC ` func (q *Queries) SelectUploadsOfSite(ctx context.Context, siteID int64) ([]Upload, error) { @@ -89,6 +90,7 @@ func (q *Queries) SelectUploadsOfSite(ctx context.Context, siteID int64) ([]Uplo &i.Guid, &i.MimeType, &i.Filename, + &i.FileSize, &i.Alt, &i.CreatedAt, ); err != nil { diff --git a/providers/db/uploads.go b/providers/db/uploads.go index f65b794..2c7dc11 100644 --- a/providers/db/uploads.go +++ b/providers/db/uploads.go @@ -37,7 +37,7 @@ func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error Guid: upload.GUID, MimeType: upload.MIMEType, Filename: upload.Filename, - CreatedAt: upload.CreatedAt, + CreatedAt: upload.CreatedAt.Unix(), Alt: upload.Alt, }); err != nil { return err @@ -77,6 +77,10 @@ func (db *Provider) SavePendingUpload(ctx context.Context, pending *models.Pendi return err } +func (db *Provider) DeletePendingUpload(ctx context.Context, guid string) error { + return db.queries.DeletePendingUpload(ctx, guid) +} + func dbUploadToUpload(row sqlgen.Upload) models.Upload { var id int64 if idVal, ok := row.ID.(int64); ok { @@ -90,7 +94,7 @@ func dbUploadToUpload(row sqlgen.Upload) models.Upload { MIMEType: row.MimeType, Filename: row.Filename, Alt: row.Alt, - CreatedAt: row.CreatedAt, + CreatedAt: time.Unix(row.CreatedAt, 0).UTC(), } } diff --git a/providers/uploadfiles/provider.go b/providers/uploadfiles/provider.go new file mode 100644 index 0000000..ea9698d --- /dev/null +++ b/providers/uploadfiles/provider.go @@ -0,0 +1,35 @@ +package uploadfiles + +import ( + "fmt" + "os" + "path/filepath" + + "lmika.dev/lmika/weiro/models" +) + +type Provider struct { + baseDir string +} + +func New(baseDir string) *Provider { + return &Provider{ + baseDir: baseDir, + } +} + +func (p *Provider) AdoptFile(site models.Site, up models.Upload, filename string) error { + baseDir := filepath.Join(p.baseDir, site.GUID, + fmt.Sprintf("%04d", up.CreatedAt.Year()), + fmt.Sprintf("%02d", up.CreatedAt.Month())) + + if err := os.MkdirAll(baseDir, 0755); err != nil { + return err + } + + targetFilename := filepath.Join(baseDir, up.GUID) + if err := os.Rename(filename, targetFilename); err != nil { + return err + } + return nil +} diff --git a/services/services.go b/services/services.go index 7bfd120..78e6a0e 100644 --- a/services/services.go +++ b/services/services.go @@ -5,6 +5,7 @@ import ( "lmika.dev/lmika/weiro/config" "lmika.dev/lmika/weiro/providers/db" + "lmika.dev/lmika/weiro/providers/uploadfiles" "lmika.dev/lmika/weiro/services/auth" "lmika.dev/lmika/weiro/services/posts" "lmika.dev/lmika/weiro/services/publisher" @@ -28,12 +29,14 @@ func New(cfg config.Config) (*Services, error) { return nil, err } + ufp := uploadfiles.New(filepath.Join(cfg.DataDir, "uploads")) + authSvc := auth.New(dbp) publisherSvc := publisher.New(dbp) publisherQueue := publisher.NewQueue(publisherSvc) postService := posts.New(dbp, publisherQueue) siteService := sites.New(dbp) - uploadService := uploads.New(dbp, filepath.Join(cfg.ScratchDir, "uploads", "pending")) + uploadService := uploads.New(dbp, ufp, filepath.Join(cfg.ScratchDir, "uploads", "pending")) return &Services{ DB: dbp, diff --git a/services/uploads/pending.go b/services/uploads/pending.go index a6dd5d3..1745610 100644 --- a/services/uploads/pending.go +++ b/services/uploads/pending.go @@ -95,12 +95,36 @@ func (s *Service) FinalizePending(ctx context.Context, pendingGUID string, expec return errors.New("invalid pending upload") } + pendingDataFilename := filepath.Join(s.pendingDir, pu.GUID+".upload") + if err := s.verifyPendingUpload(pendingDataFilename, expectedHash); err != nil { + return err + } + + newUpload := models.Upload{ + SiteID: site.ID, + GUID: models.NewNanoID(), + FileSize: pu.FileSize, + MIMEType: pu.MIMEType, + Filename: pu.Filename, + CreatedAt: time.Now().UTC(), + } + if err := s.db.SaveUpload(ctx, &newUpload); err != nil { + return err + } + + if err := s.up.AdoptFile(site, newUpload, pendingDataFilename); err != nil { + return err + } + + return nil +} + +func (s *Service) verifyPendingUpload(pendingDataFilename string, expectedHash string) error { 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 } diff --git a/services/uploads/services.go b/services/uploads/services.go index b8a264f..dc24d03 100644 --- a/services/uploads/services.go +++ b/services/uploads/services.go @@ -5,16 +5,19 @@ import ( "lmika.dev/lmika/weiro/models" "lmika.dev/lmika/weiro/providers/db" + "lmika.dev/lmika/weiro/providers/uploadfiles" ) type Service struct { db *db.Provider + up *uploadfiles.Provider pendingDir string } -func New(db *db.Provider, pendingDir string) *Service { +func New(db *db.Provider, up *uploadfiles.Provider, pendingDir string) *Service { return &Service{ db: db, + up: up, pendingDir: pendingDir, } } diff --git a/sql/queries/pending_uploads.sql b/sql/queries/pending_uploads.sql index 423b9fb..abb3241 100644 --- a/sql/queries/pending_uploads.sql +++ b/sql/queries/pending_uploads.sql @@ -11,4 +11,7 @@ INSERT INTO pending_uploads ( mime_type, upload_started_at ) VALUES (?, ?, ?, ?, ?, ?, ?) -RETURNING id; \ No newline at end of file +RETURNING id; + +-- name: DeletePendingUpload :exec +DELETE FROM pending_uploads WHERE guid = ?; \ No newline at end of file diff --git a/sql/schema/02_upload.up.sql b/sql/schema/02_upload.up.sql index 8dfec03..f87d92b 100644 --- a/sql/schema/02_upload.up.sql +++ b/sql/schema/02_upload.up.sql @@ -4,6 +4,7 @@ CREATE TABLE uploads ( guid TEXT NOT NULL, mime_type TEXT NOT NULL, filename TEXT NOT NULL, + file_size INT NOT NULL, alt TEXT NOT NULL, created_at INT NOT NULL,