nyanimedb/modules/backend/handlers/images.go
Iron_Felix c58b578023
All checks were successful
Build (backend build only) / build (push) Successful in 3m28s
fix
2025-12-20 00:17:54 +03:00

179 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"bytes"
"context"
"encoding/json"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
"mime/multipart"
"net/http"
oapi "nyanimedb/api"
sqlc "nyanimedb/sql"
"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) {
// 1. Получаем multipart body
mp := request.MultipartBody
if mp == nil {
log.Errorf("PostMedia without body")
return oapi.PostMediaUpload400JSONResponse("Multipart body is required"), nil
}
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 {
return oapi.PostMediaUpload400JSONResponse("Empty file"), nil
}
// 2. Проверка и декодирование (оставляем как было)
mimeType := http.DetectContentType(data)
var img image.Image
switch mimeType {
case "image/jpeg":
img, err = jpeg.Decode(bytes.NewReader(data))
case "image/png":
img, err = png.Decode(bytes.NewReader(data))
case "image/webp":
img, err = webp.Decode(bytes.NewReader(data))
default:
log.Errorf("PostMedia unsupported type: %s", mimeType)
return oapi.PostMediaUpload400JSONResponse("Unsupported image type"), nil
}
if err != nil {
log.Errorf("PostMedia decode error: %v", err)
return oapi.PostMediaUpload500Response{}, nil
}
// 3. Перекодируем в PNG перед отправкой
var buf bytes.Buffer
if err := imaging.Encode(&buf, img, imaging.PNG); err != nil {
log.Errorf("PostMedia encode error: %v", err)
return oapi.PostMediaUpload500Response{}, nil
}
// 4. Подготавливаем запрос к внешнему хранилищу (Image Server)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// Создаем часть с файлом
partWriter, err := writer.CreateFormFile("file", "image.png")
if err != nil {
return oapi.PostMediaUpload500Response{}, nil
}
if _, err := io.Copy(partWriter, &buf); err != nil {
return oapi.PostMediaUpload500Response{}, nil
}
// Добавляем subdir
_ = writer.WriteField("subdir", "posters")
writer.Close()
// Формируем полный URL (s.AppConfig.ImageSocket теперь base_url, например "http://storage.local")
// Убедимся, что путь не дублирует слэши
uploadURL := strings.TrimSuffix(s.ImageServerURL, "/") + "/upload"
// 5. Отправляем обычный HTTP POST
reqUpstream, err := http.NewRequestWithContext(ctx, "POST", uploadURL, body)
if err != nil {
return oapi.PostMediaUpload500Response{}, nil
}
reqUpstream.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(reqUpstream)
if err != nil {
log.Errorf("PostMedia upstream request failed: %v", err)
return oapi.PostMediaUpload500Response{}, nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Errorf("PostMedia storage returned status: %d", resp.StatusCode)
return oapi.PostMediaUpload500Response{}, nil
}
// 6. Получаем путь из ответа хранилища
var storageResp struct {
Path string `json:"path"`
}
if err := json.NewDecoder(resp.Body).Decode(&storageResp); err != nil {
return oapi.PostMediaUpload500Response{}, nil
}
// В storageResp.Path теперь лежит что-то вроде "posters/a1/b2/hash.png"
log.Infof("Successfully uploaded: %s", storageResp.Path)
params := sqlc.CreateImageParams{
StorageType: sqlc.StorageTypeTLocal,
ImagePath: storageResp.Path,
}
// TODO: param for local/s3 case
_image, err := s.db.CreateImage(ctx, params)
if err != nil {
log.Errorf("%v", err)
return oapi.PostMediaUpload500Response{}, nil
}
sType, err := sql2StorageType(&_image.StorageType)
if err != nil {
log.Errorf("%v", err)
return oapi.PostMediaUpload500Response{}, nil
}
image := oapi.Image{
Id: &_image.ID,
ImagePath: &_image.ImagePath,
StorageType: sType,
}
return oapi.PostMediaUpload200JSONResponse(image), 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
}