feat: file upload imlemented
This commit is contained in:
parent
7623adf2a7
commit
184868b142
7 changed files with 355 additions and 8 deletions
141
modules/backend/handlers/images.go
Normal file
141
modules/backend/handlers/images.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
oapi "nyanimedb/api"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
// PostMediaUpload implements oapi.StrictServerInterface.
|
||||
func (s *Server) PostMediaUpload(ctx context.Context, request oapi.PostMediaUploadRequestObject) (oapi.PostMediaUploadResponseObject, error) {
|
||||
// Получаем multipart body
|
||||
mp := request.MultipartBody
|
||||
if mp == nil {
|
||||
log.Errorf("PostMedia without body")
|
||||
return oapi.PostMediaUpload400JSONResponse("Multipart body is required"), nil
|
||||
}
|
||||
|
||||
// Парсим первую часть (предполагаем, что файл в поле "file")
|
||||
part, err := mp.NextPart()
|
||||
if err != nil {
|
||||
log.Errorf("PostMedia without file")
|
||||
return oapi.PostMediaUpload400JSONResponse("File required"), nil
|
||||
}
|
||||
defer part.Close()
|
||||
|
||||
// Читаем ВЕСЬ файл в память (для небольших изображений — нормально)
|
||||
// Если файлы могут быть большими — используйте лимитированный буфер (см. ниже)
|
||||
data, err := io.ReadAll(part)
|
||||
if err != nil {
|
||||
log.Errorf("PostMedia cannot read file")
|
||||
return oapi.PostMediaUpload400JSONResponse("File required"), nil
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
log.Errorf("PostMedia empty file")
|
||||
return oapi.PostMediaUpload400JSONResponse("Empty file"), nil
|
||||
}
|
||||
|
||||
// Проверка MIME по первым 512 байтам
|
||||
mimeType := http.DetectContentType(data)
|
||||
if mimeType != "image/jpeg" && mimeType != "image/png" && mimeType != "image/webp" {
|
||||
log.Errorf("PostMedia bad type")
|
||||
return oapi.PostMediaUpload400JSONResponse("Bad data type"), nil
|
||||
}
|
||||
|
||||
// Декодируем изображение из буфера
|
||||
var img image.Image
|
||||
switch mimeType {
|
||||
case "image/jpeg":
|
||||
{
|
||||
img, err = jpeg.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
log.Errorf("PostMedia cannot decode file: %v", err)
|
||||
return oapi.PostMediaUpload500Response{}, nil
|
||||
}
|
||||
}
|
||||
case "image/png":
|
||||
{
|
||||
img, err = png.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
log.Errorf("PostMedia cannot decode file: %v", err)
|
||||
return oapi.PostMediaUpload500Response{}, nil
|
||||
}
|
||||
}
|
||||
case "image/webp":
|
||||
{
|
||||
img, err = webp.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
log.Errorf("PostMedia cannot decode file: %v", err)
|
||||
return oapi.PostMediaUpload500Response{}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Перекодируем в чистый JPEG (без EXIF, сжатие, RGB)
|
||||
var buf bytes.Buffer
|
||||
err = imaging.Encode(&buf, img, imaging.PNG)
|
||||
if err != nil {
|
||||
log.Errorf("PostMedia failed to re-encode JPEG: %v", err)
|
||||
return oapi.PostMediaUpload500Response{}, nil
|
||||
}
|
||||
|
||||
// TODO: to delete
|
||||
filename := part.FileName()
|
||||
if filename == "" {
|
||||
filename = "upload_" + generateRandomHex(8) + ".jpg"
|
||||
} else {
|
||||
filename = sanitizeFilename(filename)
|
||||
if !strings.HasSuffix(strings.ToLower(filename), ".jpg") {
|
||||
filename += ".jpg"
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: пойти на хуй ( вызвать файловую помойку)
|
||||
err = os.WriteFile(filepath.Join("/uploads", filename), buf.Bytes(), 0644)
|
||||
if err != nil {
|
||||
log.Errorf("PostMedia failed to write: %v", err)
|
||||
return oapi.PostMediaUpload500Response{}, nil
|
||||
}
|
||||
|
||||
return oapi.PostMediaUpload200JSONResponse{}, nil
|
||||
}
|
||||
|
||||
// Вспомогательные функции — как раньше
|
||||
func generateRandomHex(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = byte('a' + (i % 16))
|
||||
}
|
||||
return fmt.Sprintf("%x", b)
|
||||
}
|
||||
|
||||
func sanitizeFilename(name string) string {
|
||||
var clean strings.Builder
|
||||
for _, r := range name {
|
||||
if (r >= 'a' && r <= 'z') ||
|
||||
(r >= 'A' && r <= 'Z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == '.' || r == '_' || r == '-' {
|
||||
clean.WriteRune(r)
|
||||
}
|
||||
}
|
||||
s := clean.String()
|
||||
if s == "" {
|
||||
return "file"
|
||||
}
|
||||
return s
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue