204 lines
4.1 KiB
Go
204 lines
4.1 KiB
Go
package routes
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/md5"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"foobar/database"
|
|
"foobar/model"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var maxFileSize int64 = 32 * 1000 * 1000
|
|
|
|
func fileUpload(w http.ResponseWriter, r *http.Request) (redirectURL, error) {
|
|
|
|
boxURL, err := GetBoxURL(r)
|
|
if err != nil {
|
|
return noRedirect, err
|
|
}
|
|
|
|
tx := database.MustBeginTx()
|
|
defer tx.Rollback()
|
|
|
|
box, err := database.SelectBox(boxURL)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) { // this is a new URL the user wants to claim for themselves
|
|
editCode := r.FormValue("edit_code")
|
|
if editCode == "" {
|
|
return noRedirect, newUserError(
|
|
"Missing edit code. Box can't be created without it",
|
|
)
|
|
}
|
|
var (
|
|
id = uuid.New()
|
|
createdAt = time.Now()
|
|
lastUpdatedAt = createdAt
|
|
header = readmeTemplate
|
|
)
|
|
newBox := model.NewBox(
|
|
id, boxURL, editCode, header, false, false, createdAt, lastUpdatedAt,
|
|
)
|
|
err = database.InsertBox(tx, newBox, editCode, header)
|
|
if err != nil {
|
|
return noRedirect, err
|
|
}
|
|
box = newBox
|
|
} else {
|
|
return noRedirect, err
|
|
}
|
|
}
|
|
|
|
filesCtx, err := GetFilesFromUploadRequest(r)
|
|
if err != nil {
|
|
return noRedirect, err
|
|
}
|
|
|
|
var files []model.File
|
|
|
|
for _, file := range filesCtx {
|
|
|
|
bytes, err := readFileBytes(file.FileHeader())
|
|
if err != nil {
|
|
return noRedirect, err
|
|
}
|
|
|
|
mime := model.FileMime(http.DetectContentType(bytes))
|
|
|
|
if file.Filename() == "README.md" {
|
|
|
|
editCode := r.FormValue("edit_code")
|
|
if editCode == "" {
|
|
return noRedirect, newUserError(
|
|
"Missing edit code. README can't be updated. " +
|
|
"If you're not attempting to update the header, we'd " +
|
|
"suggest changing \"README.md\" to a new filename.",
|
|
)
|
|
}
|
|
|
|
if editCode != box.EditCode() {
|
|
return noRedirect, newUserError(
|
|
"Wrong edit code.",
|
|
)
|
|
}
|
|
|
|
md := mdToHTML(bytes)
|
|
|
|
err := database.UpdateHeader(tx, box.ID(), md)
|
|
if err != nil {
|
|
return noRedirect, err
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return noRedirect, err
|
|
}
|
|
|
|
// FIXME: we're ignoring all other uploaded files
|
|
return redirectURL(fmt.Sprintf("/box/%s", box.Url())), nil
|
|
|
|
} else {
|
|
|
|
err = copyBytesToDisk(file.ID(), bytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
checksum := calculateMD5(bytes)
|
|
|
|
newFile := model.NewFile(
|
|
file.ID(), file.Filename(), file.Size(),
|
|
file.UploadedDate(), mime, checksum,
|
|
)
|
|
|
|
files = append(files, newFile)
|
|
}
|
|
}
|
|
|
|
for _, file := range files {
|
|
err = database.InsertFile(tx, box.ID(), file)
|
|
if err != nil {
|
|
return noRedirect, err
|
|
}
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
return noRedirect, err
|
|
}
|
|
|
|
return redirectURL(fmt.Sprintf("/box/%s/inside", box.Url())), nil
|
|
}
|
|
|
|
func calculateMD5(fileBytes []byte) model.MD5Checksum {
|
|
return md5.Sum(fileBytes)
|
|
}
|
|
|
|
func readFileBytes(fileHeader *multipart.FileHeader) ([]byte, error) {
|
|
fileReader, err := fileHeader.Open()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer fileReader.Close()
|
|
|
|
fileBytes, err := io.ReadAll(fileReader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return fileBytes, nil
|
|
}
|
|
|
|
func copyBytesToDisk(fileId uuid.UUID, fileBytes []byte) error {
|
|
fileWriter, err := os.Create(filepath.Join(
|
|
staticDir, "box", fmt.Sprintf(fileId.String())),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fileWriter.Close()
|
|
|
|
_, err = io.Copy(fileWriter, bytes.NewReader(fileBytes))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetFilesFromUploadRequest(r *http.Request) ([]model.FileUploadContext, error) {
|
|
|
|
err := r.ParseMultipartForm(int64(maxFileSize))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
headers, ok := r.MultipartForm.File["file"]
|
|
if !ok {
|
|
return nil, newUserError("no file")
|
|
}
|
|
|
|
var (
|
|
files []model.FileUploadContext
|
|
)
|
|
|
|
for _, header := range headers {
|
|
var (
|
|
id = uuid.New()
|
|
uploadedDate = time.Now()
|
|
size = header.Size
|
|
filename = model.Filename(header.Filename)
|
|
mime = model.MimeFromHeader(header.Header)
|
|
)
|
|
|
|
fileUpload := model.NewFileUploadContext(id, uploadedDate, size, filename, mime, header)
|
|
files = append(files, fileUpload)
|
|
}
|
|
return files, nil
|
|
|
|
}
|