initial commit

This commit is contained in:
silva guimaraes 2025-04-21 17:59:57 -03:00
commit c21f569144
37 changed files with 3956 additions and 0 deletions

204
routes/file.go Normal file
View file

@ -0,0 +1,204 @@
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
}