Skip to content

Commit

Permalink
Merge pull request #64 from TheWisePigeon/8-handle-file-uploads
Browse files Browse the repository at this point in the history
Handle file uploads
  • Loading branch information
joseph0x45 authored Jan 10, 2024
2 parents 1958273 + a85b0bd commit fdd37fd
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 3 deletions.
48 changes: 48 additions & 0 deletions internal/handlers/faces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package handlers

import (
"errors"
"fmt"
"log/slog"
"net/http"
"visio/internal/types"
"visio/pkg"
)

type FaceHandler struct {
logger *slog.Logger
}

func NewFaceHandler(logger *slog.Logger) *FaceHandler {
return &FaceHandler{
logger: logger,
}
}

func (h *FaceHandler) SaveFace(w http.ResponseWriter, r *http.Request) {
filePath, err := pkg.HandleFileUpload(w, r)
if err != nil {
if errors.Is(err, types.ErrFileNotFound) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(types.ErrFileNotFoundMessage))
return
}
if errors.Is(err, types.ErrUnsupportedFormat) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(types.ErrUnsupportedFormatMessage))
return
}
if errors.Is(err, types.ErrBodyTooLarge) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(types.ErrBodyTooLargeMessage))
return
}
h.logger.Error(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
fmt.Println(filePath)
w.WriteHeader(http.StatusOK)
w.Write([]byte("Ok"))
return
}
15 changes: 12 additions & 3 deletions internal/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ package types
import "errors"

var (
ErrUserNotFound = errors.New("No user found")
ErrSessionNotFound = errors.New("Session not found")
ErrDuplicatePrefix = errors.New("Duplicate key prefix")
ErrFileNotFoundMessage = "'face' field not found in request body"
ErrUnsupportedFormatMessage = "File type not supported. Only image/jpeg and image/png are."
ErrBodyTooLargeMessage = "Request body too large. Only 5Mb is allowed."
)

var (
ErrUserNotFound = errors.New("No user found")
ErrSessionNotFound = errors.New("Session not found")
ErrDuplicatePrefix = errors.New("Duplicate key prefix")
ErrFileNotFound = errors.New(ErrFileNotFoundMessage)
ErrUnsupportedFormat = errors.New(ErrUnsupportedFormatMessage)
ErrBodyTooLarge = errors.New(ErrBodyTooLargeMessage)
)
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func main() {
appHandler := handlers.NewAppHandler(keys, appLogger)
authHandler := handlers.NewAuthHandler(users, sessions, appLogger)
keyHandler := handlers.NewKeyHandler(keys, sessions, appLogger)
faceHandler := handlers.NewFaceHandler(appLogger)
authMiddleware := middlewares.NewAuthMiddleware(sessions, users, appLogger)

r := chi.NewRouter()
Expand Down Expand Up @@ -68,6 +69,10 @@ func main() {
r.With(authMiddleware.CookieAuth).Delete("/{prefix}", keyHandler.Revoke)
})

r.Route("/faces", func(r chi.Router) {
r.Post("/", faceHandler.SaveFace)
})

port := os.Getenv("PORT")
if port == "" {
panic("Unable to read PORT environment variable")
Expand Down
64 changes: 64 additions & 0 deletions pkg/uploads.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package pkg

import (
"bufio"
"fmt"
"io"
"net/http"
"os"
"visio/internal/types"
)

var imageMimeTypes = []string{
"image/jpeg",
"image/png",
}

const MaxFileSize = 5

func HandleFileUpload(w http.ResponseWriter, r *http.Request) (string, error) {
r.Body = http.MaxBytesReader(w, r.Body, MaxFileSize<<20)
reader, err := r.MultipartReader()
if err != nil {
return "", fmt.Errorf("Error while reading multipart request body: %w", err)
}
p, err := reader.NextPart()
if err != nil && err != io.EOF {
return "", fmt.Errorf("Error while reading multipart body part: %w", err)
}
if p.FormName() != "face" {
return "", types.ErrFileNotFound
}
buffer := bufio.NewReader(p)
sniffedBytes, err := buffer.Peek(512)
if err != nil {
return "", fmt.Errorf("Error while peeking through bytes: %w", err)
}
contentType := http.DetectContentType(sniffedBytes)
fileIsNotImage := true
for _, mimeType := range imageMimeTypes {
if contentType == mimeType {
fileIsNotImage = false
break
}
}
if fileIsNotImage {
return "", types.ErrUnsupportedFormat
}
f, err := os.CreateTemp("", "")
if err != nil {
return "", fmt.Errorf("Error while creating file: %w", err)
}
defer f.Close()
var maxSize int64 = MaxFileSize << 20
lmt := io.MultiReader(buffer, io.LimitReader(p, maxSize-511))
written, err := io.Copy(f, lmt)
if err != nil && err != io.EOF {
return "", types.ErrBodyTooLarge
}
if written > maxSize {
os.Remove(f.Name())
return "", fmt.Errorf("Error while deleting file: %w", err)
}
return f.Name(), nil
}

0 comments on commit fdd37fd

Please sign in to comment.