2021-08-27 17:58:52 +00:00
|
|
|
package website
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha1"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"image"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"git.handmade.network/hmn/hmn/src/db"
|
2021-09-08 00:55:52 +00:00
|
|
|
"git.handmade.network/hmn/hmn/src/models"
|
2021-08-27 17:58:52 +00:00
|
|
|
"git.handmade.network/hmn/hmn/src/oops"
|
|
|
|
)
|
|
|
|
|
2021-08-28 17:07:45 +00:00
|
|
|
type SaveImageFileResult struct {
|
2021-09-08 00:55:52 +00:00
|
|
|
ImageFile *models.ImageFile
|
2021-08-28 17:07:45 +00:00
|
|
|
ValidationError string
|
|
|
|
FatalError error
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
Reads an image file from form data and saves it to the filesystem and the database.
|
2021-08-28 17:07:45 +00:00
|
|
|
If the file doesn't exist, this does nothing and returns 0 for the image file id.
|
2021-08-27 17:58:52 +00:00
|
|
|
|
|
|
|
NOTE(ben): Someday we should replace this with the asset system.
|
|
|
|
*/
|
2021-08-28 17:07:45 +00:00
|
|
|
func SaveImageFile(c *RequestContext, dbConn db.ConnOrTx, fileFieldName string, maxSize int64, filepath string) SaveImageFileResult {
|
2021-08-27 17:58:52 +00:00
|
|
|
img, header, err := c.Req.FormFile(fileFieldName)
|
|
|
|
filename := ""
|
|
|
|
width := 0
|
|
|
|
height := 0
|
|
|
|
if err != nil && !errors.Is(err, http.ErrMissingFile) {
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
|
|
|
FatalError: oops.New(err, "failed to read uploaded file"),
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if header != nil {
|
|
|
|
if header.Size > maxSize {
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
|
|
|
ValidationError: fmt.Sprintf("Image filesize too big. Max size: %d bytes", maxSize),
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
} else {
|
|
|
|
c.Perf.StartBlock("IMAGE", "Decoding image")
|
|
|
|
config, format, err := image.DecodeConfig(img)
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
if err != nil {
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
|
|
|
ValidationError: "Image type not supported",
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
width = config.Width
|
|
|
|
height = config.Height
|
|
|
|
if width == 0 || height == 0 {
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
|
|
|
ValidationError: "Image has zero size",
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
filename = fmt.Sprintf("%s.%s", filepath, format)
|
|
|
|
storageFilename := fmt.Sprintf("public/media/%s", filename)
|
|
|
|
c.Perf.StartBlock("IMAGE", "Writing image file")
|
|
|
|
file, err := os.Create(storageFilename)
|
|
|
|
if err != nil {
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
|
|
|
FatalError: oops.New(err, "Failed to create local image file"),
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
img.Seek(0, io.SeekStart)
|
|
|
|
_, err = io.Copy(file, img)
|
|
|
|
if err != nil {
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
|
|
|
FatalError: oops.New(err, "Failed to write image to file"),
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
file.Close()
|
|
|
|
img.Close()
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.Perf.EndBlock()
|
|
|
|
|
|
|
|
c.Perf.StartBlock("SQL", "Saving image file")
|
|
|
|
if filename != "" {
|
|
|
|
hasher := sha1.New()
|
|
|
|
img.Seek(0, io.SeekStart)
|
|
|
|
io.Copy(hasher, img) // NOTE(asaf): Writing to hash.Hash never returns an error according to the docs
|
|
|
|
sha1sum := hasher.Sum(nil)
|
2022-04-16 17:49:29 +00:00
|
|
|
imageFile, err := db.QueryOne[models.ImageFile](c.Context(), dbConn,
|
2021-08-27 17:58:52 +00:00
|
|
|
`
|
2022-05-07 13:11:05 +00:00
|
|
|
INSERT INTO image_file (file, size, sha1sum, protected, width, height)
|
2021-08-27 17:58:52 +00:00
|
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
2021-09-08 00:55:52 +00:00
|
|
|
RETURNING $columns
|
2021-08-27 17:58:52 +00:00
|
|
|
`,
|
|
|
|
filename, header.Size, hex.EncodeToString(sha1sum), false, width, height,
|
2021-09-08 00:55:52 +00:00
|
|
|
)
|
2021-08-27 17:58:52 +00:00
|
|
|
if err != nil {
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
|
|
|
FatalError: oops.New(err, "Failed to insert image file row"),
|
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{
|
2022-04-16 17:49:29 +00:00
|
|
|
ImageFile: imageFile,
|
2021-08-28 17:07:45 +00:00
|
|
|
}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|
|
|
|
|
2021-08-28 17:07:45 +00:00
|
|
|
return SaveImageFileResult{}
|
2021-08-27 17:58:52 +00:00
|
|
|
}
|