Uploads #1
|
|
@ -3,13 +3,14 @@ package models
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Upload struct {
|
type Upload struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
SiteID int64 `json:"site_id"`
|
SiteID int64 `json:"site_id"`
|
||||||
GUID string `json:"guid"`
|
GUID string `json:"guid"`
|
||||||
MIMEType string `json:"mime_type"`
|
FileSize int64 `json:"file_size"`
|
||||||
Filename string `json:"filename"`
|
MIMEType string `json:"mime_type"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
Filename string `json:"filename"`
|
||||||
Alt string `json:"alt"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Alt string `json:"alt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PendingUpload struct {
|
type PendingUpload struct {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ type Upload struct {
|
||||||
Guid string
|
Guid string
|
||||||
MimeType string
|
MimeType string
|
||||||
Filename string
|
Filename string
|
||||||
|
FileSize int64
|
||||||
Alt string
|
Alt string
|
||||||
CreatedAt int64
|
CreatedAt int64
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,15 @@ import (
|
||||||
"context"
|
"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
|
const insertPendingUpload = `-- name: InsertPendingUpload :one
|
||||||
INSERT INTO pending_uploads (
|
INSERT INTO pending_uploads (
|
||||||
site_id,
|
site_id,
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectUploadByID = `-- name: SelectUploadByID :one
|
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) {
|
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.Guid,
|
||||||
&i.MimeType,
|
&i.MimeType,
|
||||||
&i.Filename,
|
&i.Filename,
|
||||||
|
&i.FileSize,
|
||||||
&i.Alt,
|
&i.Alt,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
)
|
)
|
||||||
|
|
@ -71,7 +72,7 @@ func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload,
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectUploadsOfSite = `-- name: SelectUploadsOfSite :many
|
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) {
|
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.Guid,
|
||||||
&i.MimeType,
|
&i.MimeType,
|
||||||
&i.Filename,
|
&i.Filename,
|
||||||
|
&i.FileSize,
|
||||||
&i.Alt,
|
&i.Alt,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error
|
||||||
Guid: upload.GUID,
|
Guid: upload.GUID,
|
||||||
MimeType: upload.MIMEType,
|
MimeType: upload.MIMEType,
|
||||||
Filename: upload.Filename,
|
Filename: upload.Filename,
|
||||||
CreatedAt: upload.CreatedAt,
|
CreatedAt: upload.CreatedAt.Unix(),
|
||||||
Alt: upload.Alt,
|
Alt: upload.Alt,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -77,6 +77,10 @@ func (db *Provider) SavePendingUpload(ctx context.Context, pending *models.Pendi
|
||||||
return err
|
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 {
|
func dbUploadToUpload(row sqlgen.Upload) models.Upload {
|
||||||
var id int64
|
var id int64
|
||||||
if idVal, ok := row.ID.(int64); ok {
|
if idVal, ok := row.ID.(int64); ok {
|
||||||
|
|
@ -90,7 +94,7 @@ func dbUploadToUpload(row sqlgen.Upload) models.Upload {
|
||||||
MIMEType: row.MimeType,
|
MIMEType: row.MimeType,
|
||||||
Filename: row.Filename,
|
Filename: row.Filename,
|
||||||
Alt: row.Alt,
|
Alt: row.Alt,
|
||||||
CreatedAt: row.CreatedAt,
|
CreatedAt: time.Unix(row.CreatedAt, 0).UTC(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
35
providers/uploadfiles/provider.go
Normal file
35
providers/uploadfiles/provider.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"lmika.dev/lmika/weiro/config"
|
"lmika.dev/lmika/weiro/config"
|
||||||
"lmika.dev/lmika/weiro/providers/db"
|
"lmika.dev/lmika/weiro/providers/db"
|
||||||
|
"lmika.dev/lmika/weiro/providers/uploadfiles"
|
||||||
"lmika.dev/lmika/weiro/services/auth"
|
"lmika.dev/lmika/weiro/services/auth"
|
||||||
"lmika.dev/lmika/weiro/services/posts"
|
"lmika.dev/lmika/weiro/services/posts"
|
||||||
"lmika.dev/lmika/weiro/services/publisher"
|
"lmika.dev/lmika/weiro/services/publisher"
|
||||||
|
|
@ -28,12 +29,14 @@ func New(cfg config.Config) (*Services, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ufp := uploadfiles.New(filepath.Join(cfg.DataDir, "uploads"))
|
||||||
|
|
||||||
authSvc := auth.New(dbp)
|
authSvc := auth.New(dbp)
|
||||||
publisherSvc := publisher.New(dbp)
|
publisherSvc := publisher.New(dbp)
|
||||||
publisherQueue := publisher.NewQueue(publisherSvc)
|
publisherQueue := publisher.NewQueue(publisherSvc)
|
||||||
postService := posts.New(dbp, publisherQueue)
|
postService := posts.New(dbp, publisherQueue)
|
||||||
siteService := sites.New(dbp)
|
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{
|
return &Services{
|
||||||
DB: dbp,
|
DB: dbp,
|
||||||
|
|
|
||||||
|
|
@ -95,12 +95,36 @@ func (s *Service) FinalizePending(ctx context.Context, pendingGUID string, expec
|
||||||
return errors.New("invalid pending upload")
|
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)
|
expectedHashBytes, err := hex.DecodeString(expectedHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingDataFilename := filepath.Join(s.pendingDir, pu.GUID+".upload")
|
|
||||||
if _, err := os.Stat(pendingDataFilename); err != nil {
|
if _, err := os.Stat(pendingDataFilename); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,19 @@ import (
|
||||||
|
|
||||||
"lmika.dev/lmika/weiro/models"
|
"lmika.dev/lmika/weiro/models"
|
||||||
"lmika.dev/lmika/weiro/providers/db"
|
"lmika.dev/lmika/weiro/providers/db"
|
||||||
|
"lmika.dev/lmika/weiro/providers/uploadfiles"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *db.Provider
|
db *db.Provider
|
||||||
|
up *uploadfiles.Provider
|
||||||
pendingDir string
|
pendingDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(db *db.Provider, pendingDir string) *Service {
|
func New(db *db.Provider, up *uploadfiles.Provider, pendingDir string) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
db: db,
|
db: db,
|
||||||
|
up: up,
|
||||||
pendingDir: pendingDir,
|
pendingDir: pendingDir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,7 @@ INSERT INTO pending_uploads (
|
||||||
mime_type,
|
mime_type,
|
||||||
upload_started_at
|
upload_started_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
|
|
||||||
|
-- name: DeletePendingUpload :exec
|
||||||
|
DELETE FROM pending_uploads WHERE guid = ?;
|
||||||
|
|
@ -4,6 +4,7 @@ CREATE TABLE uploads (
|
||||||
guid TEXT NOT NULL,
|
guid TEXT NOT NULL,
|
||||||
mime_type TEXT NOT NULL,
|
mime_type TEXT NOT NULL,
|
||||||
filename TEXT NOT NULL,
|
filename TEXT NOT NULL,
|
||||||
|
file_size INT NOT NULL,
|
||||||
alt TEXT NOT NULL,
|
alt TEXT NOT NULL,
|
||||||
created_at INT NOT NULL,
|
created_at INT NOT NULL,
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue