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) {
|
for (let file of files) {
|
||||||
await this._doUpload(file);
|
await this._doUpload(file);
|
||||||
}
|
}
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _doUpload(file) {
|
async _doUpload(file) {
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,8 @@ Starting weiro without any arguments will start the server.
|
||||||
siteGroup.Delete("/posts/:postID", ph.Delete)
|
siteGroup.Delete("/posts/:postID", ph.Delete)
|
||||||
|
|
||||||
siteGroup.Get("/uploads", uh.Index)
|
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", uh.New)
|
||||||
siteGroup.Post("/uploads/pending/:guid", uh.UploadPart)
|
siteGroup.Post("/uploads/pending/:guid", uh.UploadPart)
|
||||||
siteGroup.Post("/uploads/pending/:guid/finalize", uh.UploadComplete)
|
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/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // 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/cenkalti/backoff/v4 v4.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // 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/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.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/libc v1.67.6 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // 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 h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
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/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 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/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
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=
|
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 h1:DBEJahbQO7W3uEmAOQGg1URBWYimg0ClWHi83M2MZwk=
|
||||||
lmika.dev/pkg/litemigrate v0.1.0/go.mod h1:GQWWDiMZGQaVspcwKNq8vIBPN5H+KsUo/VBIeh9OfLg=
|
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 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
"lmika.dev/lmika/weiro/services/uploads"
|
"lmika.dev/lmika/weiro/services/uploads"
|
||||||
|
"lmika.dev/pkg/modash/moslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UploadsHandler struct {
|
type UploadsHandler struct {
|
||||||
|
|
@ -10,7 +17,65 @@ type UploadsHandler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uh UploadsHandler) Index(c fiber.Ctx) error {
|
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 {
|
func (uh UploadsHandler) New(c fiber.Ctx) error {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ type Upload struct {
|
||||||
MIMEType string `json:"mime_type"`
|
MIMEType string `json:"mime_type"`
|
||||||
Filename string `json:"filename"`
|
Filename string `json:"filename"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
Alt string `json:"alt"`
|
Alt string `json:"alt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
package sqlgen
|
package sqlgen
|
||||||
|
|
||||||
type PendingUpload struct {
|
type PendingUpload struct {
|
||||||
ID interface{}
|
ID int64
|
||||||
SiteID int64
|
SiteID int64
|
||||||
Guid string
|
Guid string
|
||||||
UserID int64
|
UserID int64
|
||||||
|
|
@ -50,12 +50,13 @@ type Site struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Upload struct {
|
type Upload struct {
|
||||||
ID interface{}
|
ID int64
|
||||||
SiteID int64
|
SiteID int64
|
||||||
Guid string
|
Guid string
|
||||||
MimeType string
|
MimeType string
|
||||||
Filename string
|
Filename string
|
||||||
FileSize int64
|
FileSize int64
|
||||||
|
Slug string
|
||||||
Alt string
|
Alt string
|
||||||
CreatedAt int64
|
CreatedAt int64
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ type InsertPendingUploadParams struct {
|
||||||
UploadStartedAt int64
|
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,
|
row := q.db.QueryRowContext(ctx, insertPendingUpload,
|
||||||
arg.SiteID,
|
arg.SiteID,
|
||||||
arg.Guid,
|
arg.Guid,
|
||||||
|
|
@ -51,7 +51,7 @@ func (q *Queries) InsertPendingUpload(ctx context.Context, arg InsertPendingUplo
|
||||||
arg.MimeType,
|
arg.MimeType,
|
||||||
arg.UploadStartedAt,
|
arg.UploadStartedAt,
|
||||||
)
|
)
|
||||||
var id interface{}
|
var id int64
|
||||||
err := row.Scan(&id)
|
err := row.Scan(&id)
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const deleteUpload = `-- name: DeleteUpload :exec
|
||||||
DELETE FROM uploads WHERE id = ?
|
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)
|
_, err := q.db.ExecContext(ctx, deleteUpload, id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -24,9 +24,11 @@ INSERT INTO uploads (
|
||||||
guid,
|
guid,
|
||||||
mime_type,
|
mime_type,
|
||||||
filename,
|
filename,
|
||||||
created_at,
|
file_size,
|
||||||
alt
|
slug,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)
|
alt,
|
||||||
|
created_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -35,8 +37,10 @@ type InsertUploadParams struct {
|
||||||
Guid string
|
Guid string
|
||||||
MimeType string
|
MimeType string
|
||||||
Filename string
|
Filename string
|
||||||
CreatedAt int64
|
FileSize int64
|
||||||
|
Slug string
|
||||||
Alt string
|
Alt string
|
||||||
|
CreatedAt int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InsertUpload(ctx context.Context, arg InsertUploadParams) error {
|
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.Guid,
|
||||||
arg.MimeType,
|
arg.MimeType,
|
||||||
arg.Filename,
|
arg.Filename,
|
||||||
arg.CreatedAt,
|
arg.FileSize,
|
||||||
|
arg.Slug,
|
||||||
arg.Alt,
|
arg.Alt,
|
||||||
|
arg.CreatedAt,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectUploadByID = `-- name: SelectUploadByID :one
|
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)
|
row := q.db.QueryRowContext(ctx, selectUploadByID, id)
|
||||||
var i Upload
|
var i Upload
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -65,6 +71,33 @@ func (q *Queries) SelectUploadByID(ctx context.Context, id interface{}) (Upload,
|
||||||
&i.MimeType,
|
&i.MimeType,
|
||||||
&i.Filename,
|
&i.Filename,
|
||||||
&i.FileSize,
|
&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.Alt,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
)
|
)
|
||||||
|
|
@ -72,7 +105,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, 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) {
|
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.MimeType,
|
||||||
&i.Filename,
|
&i.Filename,
|
||||||
&i.FileSize,
|
&i.FileSize,
|
||||||
|
&i.Slug,
|
||||||
&i.Alt,
|
&i.Alt,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -113,7 +147,7 @@ UPDATE uploads SET alt = ? WHERE id = ?
|
||||||
|
|
||||||
type UpdateUploadParams struct {
|
type UpdateUploadParams struct {
|
||||||
Alt string
|
Alt string
|
||||||
ID interface{}
|
ID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateUpload(ctx context.Context, arg UpdateUploadParams) error {
|
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
|
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 {
|
func (db *Provider) SaveUpload(ctx context.Context, upload *models.Upload) error {
|
||||||
if upload.ID == 0 {
|
if upload.ID == 0 {
|
||||||
if err := db.queries.InsertUpload(ctx, sqlgen.InsertUploadParams{
|
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,
|
Guid: upload.GUID,
|
||||||
MimeType: upload.MIMEType,
|
MimeType: upload.MIMEType,
|
||||||
Filename: upload.Filename,
|
Filename: upload.Filename,
|
||||||
CreatedAt: upload.CreatedAt.Unix(),
|
FileSize: upload.FileSize,
|
||||||
|
Slug: upload.Slug,
|
||||||
Alt: upload.Alt,
|
Alt: upload.Alt,
|
||||||
|
CreatedAt: upload.CreatedAt.Unix(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -82,17 +96,14 @@ func (db *Provider) DeletePendingUpload(ctx context.Context, guid string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func dbUploadToUpload(row sqlgen.Upload) models.Upload {
|
func dbUploadToUpload(row sqlgen.Upload) models.Upload {
|
||||||
var id int64
|
|
||||||
if idVal, ok := row.ID.(int64); ok {
|
|
||||||
id = idVal
|
|
||||||
}
|
|
||||||
|
|
||||||
return models.Upload{
|
return models.Upload{
|
||||||
ID: id,
|
ID: row.ID,
|
||||||
SiteID: row.SiteID,
|
SiteID: row.SiteID,
|
||||||
GUID: row.Guid,
|
GUID: row.Guid,
|
||||||
MIMEType: row.MimeType,
|
MIMEType: row.MimeType,
|
||||||
|
FileSize: row.FileSize,
|
||||||
Filename: row.Filename,
|
Filename: row.Filename,
|
||||||
|
Slug: row.Slug,
|
||||||
Alt: row.Alt,
|
Alt: row.Alt,
|
||||||
CreatedAt: time.Unix(row.CreatedAt, 0).UTC(),
|
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
|
package uploadfiles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
|
@ -19,17 +19,24 @@ func New(baseDir string) *Provider {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) AdoptFile(site models.Site, up models.Upload, filename string) error {
|
func (p *Provider) AdoptFile(site models.Site, up models.Upload, filename string) error {
|
||||||
baseDir := filepath.Join(p.baseDir, site.GUID,
|
fullPath := p.uploadFileName(site, up)
|
||||||
fmt.Sprintf("%04d", up.CreatedAt.Year()),
|
baseDir := filepath.Dir(fullPath)
|
||||||
fmt.Sprintf("%02d", up.CreatedAt.Month()))
|
|
||||||
|
|
||||||
if err := os.MkdirAll(baseDir, 0755); err != nil {
|
if err := os.MkdirAll(baseDir, 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetFilename := filepath.Join(baseDir, up.GUID)
|
if err := os.Rename(filename, fullPath); err != nil {
|
||||||
if err := os.Rename(filename, targetFilename); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -100,13 +102,22 @@ func (s *Service) FinalizePending(ctx context.Context, pendingGUID string, expec
|
||||||
return err
|
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{
|
newUpload := models.Upload{
|
||||||
SiteID: site.ID,
|
SiteID: site.ID,
|
||||||
GUID: models.NewNanoID(),
|
GUID: models.NewNanoID(),
|
||||||
FileSize: pu.FileSize,
|
FileSize: pu.FileSize,
|
||||||
MIMEType: pu.MIMEType,
|
MIMEType: pu.MIMEType,
|
||||||
Filename: pu.Filename,
|
Filename: pu.Filename,
|
||||||
CreatedAt: time.Now().UTC(),
|
CreatedAt: newTime,
|
||||||
|
Slug: newSlug,
|
||||||
}
|
}
|
||||||
if err := s.db.SaveUpload(ctx, &newUpload); err != nil {
|
if err := s.db.SaveUpload(ctx, &newUpload); err != nil {
|
||||||
return err
|
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 {
|
if err := s.up.AdoptFile(site, newUpload, pendingDataFilename); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
SELECT * FROM uploads WHERE site_id = ? ORDER BY created_at DESC;
|
SELECT * FROM uploads WHERE site_id = ? ORDER BY created_at DESC;
|
||||||
|
|
||||||
-- name: SelectUploadByID :one
|
-- 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
|
-- name: InsertUpload :exec
|
||||||
INSERT INTO uploads (
|
INSERT INTO uploads (
|
||||||
|
|
@ -10,9 +13,11 @@ INSERT INTO uploads (
|
||||||
guid,
|
guid,
|
||||||
mime_type,
|
mime_type,
|
||||||
filename,
|
filename,
|
||||||
created_at,
|
file_size,
|
||||||
alt
|
slug,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?)
|
alt,
|
||||||
|
created_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
|
|
||||||
-- name: UpdateUpload :exec
|
-- name: UpdateUpload :exec
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
CREATE TABLE uploads (
|
CREATE TABLE uploads (
|
||||||
id SERIAL PRIMARY KEY,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
site_id INT NOT NULL,
|
site_id INT NOT NULL,
|
||||||
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,
|
file_size INT NOT NULL,
|
||||||
|
slug TEXT NOT NULL,
|
||||||
alt TEXT NOT NULL,
|
alt TEXT NOT NULL,
|
||||||
created_at INT NOT NULL,
|
created_at INT NOT NULL,
|
||||||
|
|
||||||
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE
|
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
CREATE INDEX idx_uploads_site ON uploads (site_id);
|
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 (
|
CREATE TABLE pending_uploads (
|
||||||
id SERIAL PRIMARY KEY,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
site_id INT NOT NULL,
|
site_id INT NOT NULL,
|
||||||
guid TEXT NOT NULL,
|
guid TEXT NOT NULL,
|
||||||
user_id INT NOT NULL,
|
user_id INT NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,18 @@
|
||||||
data-controller="upload" data-upload-site-id-value="{{ .site.ID }}">
|
data-controller="upload" data-upload-site-id-value="{{ .site.ID }}">
|
||||||
<button class="btn btn-success" data-action="upload#upload">Upload</button>
|
<button class="btn btn-success" data-action="upload#upload">Upload</button>
|
||||||
</div>
|
</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>
|
</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