Started working on the view upload page
This commit is contained in:
parent
0a9af9cde8
commit
48f39133d7
|
|
@ -33,6 +33,7 @@ export default class UploadController extends Controller {
|
|||
for (let file of files) {
|
||||
await this._doUpload(file);
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
async _doUpload(file) {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,8 @@ Starting weiro without any arguments will start the server.
|
|||
siteGroup.Delete("/posts/:postID", ph.Delete)
|
||||
|
||||
siteGroup.Get("/uploads", uh.Index)
|
||||
siteGroup.Get("/uploads/:uploadID", uh.Show)
|
||||
siteGroup.Get("/uploads/:uploadID/raw", uh.ShowRaw)
|
||||
siteGroup.Post("/uploads/pending", uh.New)
|
||||
siteGroup.Post("/uploads/pending/:guid", uh.UploadPart)
|
||||
siteGroup.Post("/uploads/pending/:guid/finalize", uh.UploadComplete)
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -23,6 +23,7 @@ require (
|
|||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||
github.com/barasher/go-exiftool v1.10.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
|
|
@ -80,6 +81,7 @@ require (
|
|||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
lmika.dev/pkg/modash v0.1.1-0.20260302110707-31c6b125c997 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -57,6 +57,8 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o
|
|||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
||||
github.com/barasher/go-exiftool v1.10.0 h1:f5JY5jc42M7tzR6tbL9508S2IXdIcG9QyieEXNMpIhs=
|
||||
github.com/barasher/go-exiftool v1.10.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
|
|
@ -731,6 +733,10 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
lmika.dev/pkg/litemigrate v0.1.0 h1:DBEJahbQO7W3uEmAOQGg1URBWYimg0ClWHi83M2MZwk=
|
||||
lmika.dev/pkg/litemigrate v0.1.0/go.mod h1:GQWWDiMZGQaVspcwKNq8vIBPN5H+KsUo/VBIeh9OfLg=
|
||||
lmika.dev/pkg/modash v0.1.0 h1:fltroSvP0nKj9K0E6G+S9LULvB9Qhj47+SZ2b9v/v/c=
|
||||
lmika.dev/pkg/modash v0.1.0/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
|
||||
lmika.dev/pkg/modash v0.1.1-0.20260302110707-31c6b125c997 h1:XGdi5Ca5IJgGXPd057R2QHENQ6PwIUUfhBTGGF6yuLM=
|
||||
lmika.dev/pkg/modash v0.1.1-0.20260302110707-31c6b125c997/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"lmika.dev/lmika/weiro/services/uploads"
|
||||
"lmika.dev/pkg/modash/moslice"
|
||||
)
|
||||
|
||||
type UploadsHandler struct {
|
||||
|
|
@ -10,7 +17,65 @@ type UploadsHandler struct {
|
|||
}
|
||||
|
||||
func (uh UploadsHandler) Index(c fiber.Ctx) error {
|
||||
return c.Render("uploads/index", nil)
|
||||
uploads, err := uh.UploadsService.ListUploads(c.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows := moslice.Batch(uploads, 5)
|
||||
|
||||
return c.Render("uploads/index", fiber.Map{"uploads": rows})
|
||||
}
|
||||
|
||||
func (uh UploadsHandler) Show(c fiber.Ctx) error {
|
||||
uploadIDStr := c.Params("uploadID")
|
||||
if uploadIDStr == "" {
|
||||
return fiber.ErrBadRequest
|
||||
}
|
||||
uploadID, err := strconv.ParseInt(uploadIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return fiber.ErrBadRequest
|
||||
}
|
||||
|
||||
upload, err := uh.UploadsService.FetchUpload(c.Context(), uploadID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("uploads/show", fiber.Map{"upload": upload})
|
||||
}
|
||||
|
||||
func (uh UploadsHandler) ShowRaw(c fiber.Ctx) error {
|
||||
uploadIDStr := c.Params("uploadID")
|
||||
if uploadIDStr == "" {
|
||||
return fiber.ErrBadRequest
|
||||
}
|
||||
uploadID, err := strconv.ParseInt(uploadIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return fiber.ErrBadRequest
|
||||
}
|
||||
|
||||
upload, rwFn, err := uh.UploadsService.OpenUpload(c.Context(), uploadID)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return err
|
||||
}
|
||||
|
||||
c.Set("Content-Type", upload.MIMEType)
|
||||
c.Status(http.StatusOK)
|
||||
return c.SendStreamWriter(func(w *bufio.Writer) {
|
||||
rw, err := rwFn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rw.Close()
|
||||
|
||||
_, err = io.Copy(w, rw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (uh UploadsHandler) New(c fiber.Ctx) error {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ type Upload struct {
|
|||
MIMEType string `json:"mime_type"`
|
||||
Filename string `json:"filename"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Slug string `json:"slug"`
|
||||
Alt string `json:"alt"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
package sqlgen
|
||||
|
||||
type PendingUpload struct {
|
||||
ID interface{}
|
||||
ID int64
|
||||
SiteID int64
|
||||
Guid string
|
||||
UserID int64
|
||||
|
|
@ -50,12 +50,13 @@ type Site struct {
|
|||
}
|
||||
|
||||
type Upload struct {
|
||||
ID interface{}
|
||||
ID int64
|
||||
SiteID int64
|
||||
Guid string
|
||||
MimeType string
|
||||
Filename string
|
||||
FileSize int64
|
||||
Slug string
|
||||
Alt string
|
||||
CreatedAt int64
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ type InsertPendingUploadParams struct {
|
|||
UploadStartedAt int64
|
||||
}
|
||||
|
||||
func (q *Queries) InsertPendingUpload(ctx context.Context, arg InsertPendingUploadParams) (interface{}, error) {
|
||||
func (q *Queries) InsertPendingUpload(ctx context.Context, arg InsertPendingUploadParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertPendingUpload,
|
||||
arg.SiteID,
|
||||
arg.Guid,
|
||||
|
|
@ -51,7 +51,7 @@ func (q *Queries) InsertPendingUpload(ctx context.Context, arg InsertPendingUplo
|
|||
arg.MimeType,
|
||||
arg.UploadStartedAt,
|
||||
)
|
||||
var id interface{}
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const deleteUpload = `-- name: DeleteUpload :exec
|
|||
DELETE FROM uploads WHERE id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteUpload(ctx context.Context, id interface{}) error {
|
||||
func (q *Queries) DeleteUpload(ctx context.Context, id int64) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteUpload, id)
|
||||
return err
|
||||
}
|
||||
|
|
@ -24,9 +24,11 @@ INSERT INTO uploads (
|
|||
guid,
|
||||
mime_type,
|
||||
filename,
|
||||
created_at,
|
||||
alt
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
file_size,
|
||||
slug,
|
||||
alt,
|
||||
created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
|
|
@ -35,8 +37,10 @@ type InsertUploadParams struct {
|
|||
Guid string
|
||||
MimeType string
|
||||
Filename string
|
||||
CreatedAt int64
|
||||
FileSize int64
|
||||
Slug string
|
||||
Alt string
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) error {
|
||||
|
|
@ -45,17 +49,19 @@ func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) erro
|
|||
arg.Guid,
|
||||
arg.MimeType,
|
||||
arg.Filename,
|
||||
arg.CreatedAt,
|
||||
arg.FileSize,
|
||||
arg.Slug,
|
||||
arg.Alt,
|
||||
arg.CreatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const selectUploadByID = `-- name: SelectUploadByID :one
|
||||
SELECT id, site_id, guid, mime_type, filename, file_size, alt, created_at FROM uploads WHERE id = ?
|
||||
SELECT id, site_id, guid, mime_type, filename, file_size, slug, alt, created_at FROM uploads WHERE id = ? LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload, error) {
|
||||
func (q *Queries) SelectUploadByID(ctx context.Context, id int64) (Upload, error) {
|
||||
row := q.db.QueryRowContext(ctx, selectUploadByID, id)
|
||||
var i Upload
|
||||
err := row.Scan(
|
||||
|
|
@ -65,6 +71,33 @@ func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload,
|
|||
&i.MimeType,
|
||||
&i.Filename,
|
||||
&i.FileSize,
|
||||
&i.Slug,
|
||||
&i.Alt,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const selectUploadBySiteIDAndSlug = `-- name: SelectUploadBySiteIDAndSlug :one
|
||||
SELECT id, site_id, guid, mime_type, filename, file_size, slug, alt, created_at FROM uploads WHERE site_id = ? AND slug = ? LIMIT 1
|
||||
`
|
||||
|
||||
type SelectUploadBySiteIDAndSlugParams struct {
|
||||
SiteID int64
|
||||
Slug string
|
||||
}
|
||||
|
||||
func (q *Queries) SelectUploadBySiteIDAndSlug(ctx context.Context, arg SelectUploadBySiteIDAndSlugParams) (Upload, error) {
|
||||
row := q.db.QueryRowContext(ctx, selectUploadBySiteIDAndSlug, arg.SiteID, arg.Slug)
|
||||
var i Upload
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SiteID,
|
||||
&i.Guid,
|
||||
&i.MimeType,
|
||||
&i.Filename,
|
||||
&i.FileSize,
|
||||
&i.Slug,
|
||||
&i.Alt,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
|
|
@ -72,7 +105,7 @@ func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload,
|
|||
}
|
||||
|
||||
const selectUploadsOfSite = `-- name: SelectUploadsOfSite :many
|
||||
SELECT id, site_id, guid, mime_type, filename, file_size, alt, created_at FROM uploads WHERE site_id = ? ORDER BY created_at DESC
|
||||
SELECT id, site_id, guid, mime_type, filename, file_size, slug, alt, created_at FROM uploads WHERE site_id = ? ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
func (q *Queries) SelectUploadsOfSite(ctx context.Context, siteID int64) ([]Upload, error) {
|
||||
|
|
@ -91,6 +124,7 @@ func (q *Queries) SelectUploadsOfSite(ctx context.Context, siteID int64) ([]Uplo
|
|||
&i.MimeType,
|
||||
&i.Filename,
|
||||
&i.FileSize,
|
||||
&i.Slug,
|
||||
&i.Alt,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
|
|
@ -113,7 +147,7 @@ UPDATE uploads SET alt = ? WHERE id = ?
|
|||
|
||||
type UpdateUploadParams struct {
|
||||
Alt string
|
||||
ID interface{}
|
||||
ID int64
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUpload(ctx context.Context, arg UpdateUploadParams) error {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,18 @@ func (db *Provider) SelectUploadsOfSite(ctx context.Context, siteID int64) ([]mo
|
|||
return uploads, nil
|
||||
}
|
||||
|
||||
func (db *Provider) SelectUploadBySiteIDAndSlug(ctx context.Context, siteID int64, slug string) (models.Upload, error) {
|
||||
row, err := db.queries.SelectUploadBySiteIDAndSlug(ctx, sqlgen.SelectUploadBySiteIDAndSlugParams{
|
||||
SiteID: siteID,
|
||||
Slug: slug,
|
||||
})
|
||||
if err != nil {
|
||||
return models.Upload{}, err
|
||||
}
|
||||
|
||||
return dbUploadToUpload(row), nil
|
||||
}
|
||||
|
||||
func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error {
|
||||
if upload.ID == 0 {
|
||||
if err := db.queries.InsertUpload(ctx, sqlgen.InsertUploadParams{
|
||||
|
|
@ -37,8 +49,10 @@ func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error
|
|||
Guid: upload.GUID,
|
||||
MimeType: upload.MIMEType,
|
||||
Filename: upload.Filename,
|
||||
CreatedAt: upload.CreatedAt.Unix(),
|
||||
FileSize: upload.FileSize,
|
||||
Slug: upload.Slug,
|
||||
Alt: upload.Alt,
|
||||
CreatedAt: upload.CreatedAt.Unix(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -82,17 +96,14 @@ func (db *Provider) DeletePendingUpload(ctx context.Context, guid string) error
|
|||
}
|
||||
|
||||
func dbUploadToUpload(row sqlgen.Upload) models.Upload {
|
||||
var id int64
|
||||
if idVal, ok := row.ID.(int64); ok {
|
||||
id = idVal
|
||||
}
|
||||
|
||||
return models.Upload{
|
||||
ID: id,
|
||||
ID: row.ID,
|
||||
SiteID: row.SiteID,
|
||||
GUID: row.Guid,
|
||||
MIMEType: row.MimeType,
|
||||
FileSize: row.FileSize,
|
||||
Filename: row.Filename,
|
||||
Slug: row.Slug,
|
||||
Alt: row.Alt,
|
||||
CreatedAt: time.Unix(row.CreatedAt, 0).UTC(),
|
||||
}
|
||||
|
|
|
|||
32
providers/uploadfiles/exif.go
Normal file
32
providers/uploadfiles/exif.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package uploadfiles
|
||||
|
||||
import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/barasher/go-exiftool"
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
)
|
||||
|
||||
func (p *Provider) StripeEXIFData(site models.Site, up models.Upload) error {
|
||||
uploadFilename := p.uploadFileName(site, up)
|
||||
|
||||
et, err := exiftool.NewExiftool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer et.Close()
|
||||
|
||||
fileInfos := et.ExtractMetadata(uploadFilename)
|
||||
if len(fileInfos) == 0 {
|
||||
return errors.New("no exif data found")
|
||||
}
|
||||
fileInfo := fileInfos[0]
|
||||
fileInfo.ClearAll()
|
||||
|
||||
fileOut := []exiftool.FileMetadata{fileInfo}
|
||||
et.WriteMetadata(fileOut)
|
||||
if fileOut[0].Err != nil {
|
||||
return fileOut[0].Err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package uploadfiles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -19,17 +19,24 @@ func New(baseDir string) *Provider {
|
|||
}
|
||||
|
||||
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()))
|
||||
fullPath := p.uploadFileName(site, up)
|
||||
baseDir := filepath.Dir(fullPath)
|
||||
|
||||
if err := os.MkdirAll(baseDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetFilename := filepath.Join(baseDir, up.GUID)
|
||||
if err := os.Rename(filename, targetFilename); err != nil {
|
||||
if err := os.Rename(filename, fullPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) OpenUpload(site models.Site, up models.Upload) (io.ReadCloser, error) {
|
||||
fullPath := p.uploadFileName(site, up)
|
||||
return os.Open(fullPath)
|
||||
}
|
||||
|
||||
func (p *Provider) uploadFileName(site models.Site, up models.Upload) string {
|
||||
return filepath.Join(p.baseDir, site.GUID, up.Slug)
|
||||
}
|
||||
|
|
|
|||
75
services/uploads/manage.go
Normal file
75
services/uploads/manage.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package uploads
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"lmika.dev/lmika/weiro/models"
|
||||
)
|
||||
|
||||
type UploadWithURL struct {
|
||||
Upload models.Upload
|
||||
URL string
|
||||
}
|
||||
|
||||
func (s *Service) FetchUpload(ctx context.Context, uploadID int64) (res UploadWithURL, _ error) {
|
||||
site, _, err := s.fetchSiteAndUser(ctx)
|
||||
if err != nil {
|
||||
return UploadWithURL{}, err
|
||||
}
|
||||
|
||||
upload, err := s.db.SelectUploadByID(ctx, uploadID)
|
||||
if err != nil {
|
||||
return UploadWithURL{}, err
|
||||
}
|
||||
|
||||
return UploadWithURL{
|
||||
Upload: upload,
|
||||
URL: fmt.Sprintf("/sites/%v/uploads/%v/raw", site.ID, upload.ID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) ListUploads(ctx context.Context) (res []UploadWithURL, _ error) {
|
||||
site, _, err := s.fetchSiteAndUser(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uploads, err := s.db.SelectUploadsOfSite(ctx, site.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = make([]UploadWithURL, len(uploads))
|
||||
for i, upload := range uploads {
|
||||
res[i] = UploadWithURL{
|
||||
Upload: upload,
|
||||
URL: fmt.Sprintf("/sites/%v/uploads/%v/raw", site.ID, upload.ID),
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Service) OpenUpload(ctx context.Context, id int64) (models.Upload, func() (io.ReadCloser, error), error) {
|
||||
site, _, err := s.fetchSiteAndUser(ctx)
|
||||
if err != nil {
|
||||
return models.Upload{}, nil, err
|
||||
}
|
||||
|
||||
upload, err := s.db.SelectUploadByID(ctx, id)
|
||||
if err != nil {
|
||||
return models.Upload{}, nil, err
|
||||
} else if upload.SiteID != site.ID {
|
||||
return models.Upload{}, nil, models.NotFoundError
|
||||
}
|
||||
|
||||
return upload, func() (io.ReadCloser, error) {
|
||||
rw, err := s.up.OpenUpload(site, upload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rw, nil
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ import (
|
|||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
|
@ -100,13 +102,22 @@ func (s *Service) FinalizePending(ctx context.Context, pendingGUID string, expec
|
|||
return err
|
||||
}
|
||||
|
||||
newUploadGUID := models.NewNanoID()
|
||||
newTime := time.Now().UTC()
|
||||
newSlug := filepath.Join(
|
||||
fmt.Sprintf("%04d", newTime.Year()),
|
||||
fmt.Sprintf("%02d", newTime.Month()),
|
||||
newUploadGUID+filepath.Ext(pu.Filename),
|
||||
)
|
||||
|
||||
newUpload := models.Upload{
|
||||
SiteID: site.ID,
|
||||
GUID: models.NewNanoID(),
|
||||
FileSize: pu.FileSize,
|
||||
MIMEType: pu.MIMEType,
|
||||
Filename: pu.Filename,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
CreatedAt: newTime,
|
||||
Slug: newSlug,
|
||||
}
|
||||
if err := s.db.SaveUpload(ctx, &newUpload); err != nil {
|
||||
return err
|
||||
|
|
@ -115,6 +126,12 @@ func (s *Service) FinalizePending(ctx context.Context, pendingGUID string, expec
|
|||
if err := s.up.AdoptFile(site, newUpload, pendingDataFilename); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.db.DeletePendingUpload(ctx, newUpload.GUID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.up.StripeEXIFData(site, newUpload); err != nil {
|
||||
log.Printf("warn: failed to extract exif data from %s: %v\n", newUpload.Slug, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
SELECT * FROM uploads WHERE site_id = ? ORDER BY created_at DESC;
|
||||
|
||||
-- name: SelectUploadByID :one
|
||||
SELECT * FROM uploads WHERE id = ?;
|
||||
SELECT * FROM uploads WHERE id = ? LIMIT 1;
|
||||
|
||||
-- name: SelectUploadBySiteIDAndSlug :one
|
||||
SELECT * FROM uploads WHERE site_id = ? AND slug = ? LIMIT 1;
|
||||
|
||||
-- name: InsertUpload :exec
|
||||
INSERT INTO uploads (
|
||||
|
|
@ -10,9 +13,11 @@ INSERT INTO uploads (
|
|||
guid,
|
||||
mime_type,
|
||||
filename,
|
||||
created_at,
|
||||
alt
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
file_size,
|
||||
slug,
|
||||
alt,
|
||||
created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING id;
|
||||
|
||||
-- name: UpdateUpload :exec
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
CREATE TABLE uploads (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_id INT NOT NULL,
|
||||
guid TEXT NOT NULL,
|
||||
mime_type TEXT NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
file_size INT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
alt TEXT NOT NULL,
|
||||
created_at INT NOT NULL,
|
||||
|
||||
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX idx_uploads_site ON uploads (site_id);
|
||||
CREATE UNIQUE INDEX idx_uploads_guid ON sites (guid);
|
||||
CREATE UNIQUE INDEX idx_uploads_guid ON uploads (guid);
|
||||
CREATE UNIQUE INDEX idx_uploads_site_slug ON uploads (site_id, slug);
|
||||
|
||||
CREATE TABLE pending_uploads (
|
||||
id SERIAL PRIMARY KEY,
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_id INT NOT NULL,
|
||||
guid TEXT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
|
|
|
|||
|
|
@ -3,4 +3,18 @@
|
|||
data-controller="upload" data-upload-site-id-value="{{ .site.ID }}">
|
||||
<button class="btn btn-success" data-action="upload#upload">Upload</button>
|
||||
</div>
|
||||
|
||||
{{ range .uploads }}
|
||||
<div class="row row-cols-5">
|
||||
{{ range . }}
|
||||
<div class="col">
|
||||
<a href="/sites/{{ $.site.ID }}/uploads/{{ .Upload.ID }}">
|
||||
<div class="ratio ratio-1x1 m-2">
|
||||
<img src="{{ .URL }}" alt="{{ .Upload.Alt }}" class="img-fluid object-fit-contain">
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</main>
|
||||
8
views/uploads/show.html
Normal file
8
views/uploads/show.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<main class="container">
|
||||
<div class="my-4 d-flex justify-content-between align-items-baseline"
|
||||
data-controller="upload" data-upload-site-id-value="{{ .site.ID }}">
|
||||
<button class="btn" data-action="upload#upload">Copy HTML</button>
|
||||
</div>
|
||||
|
||||
<img src="{{ .URL }}" alt="{{ .Upload.Alt }}" class="img-fluid">
|
||||
</main>
|
||||
Loading…
Reference in a new issue