Files
Bridge-and-Join-s/internal/lkgateway/branding.go
T

96 lines
3.5 KiB
Go

package lkgateway
// branding.go — настройка логотипа организации в шапке отчётов (загрузка своего
// изображения через UI). Хранится в настройках (Branding.LogoDataURI) как data-URI;
// пусто → встроенный логотип по умолчанию. См. orgLogoIMG в fontvielle_logo.go.
import (
"encoding/base64"
"io"
"net/http"
"strings"
)
const maxLogoBytes = 512 << 10 // 512 КБ — логотип встраивается в каждый отчёт
// saveLogo принимает загруженное изображение и сохраняет его как логотип отчётов.
func (h *setupHandlers) saveLogo(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
if err := r.ParseMultipartForm(maxLogoBytes + (64 << 10)); err != nil {
setupFlash(w, r, "Логотип: не удалось прочитать форму: "+err.Error())
return
}
file, header, err := r.FormFile("logo")
if err != nil {
setupFlash(w, r, "Логотип: файл не выбран")
return
}
defer file.Close()
data, err := io.ReadAll(io.LimitReader(file, maxLogoBytes+1))
if err != nil {
setupFlash(w, r, "Логотип: ошибка чтения файла: "+err.Error())
return
}
if len(data) == 0 {
setupFlash(w, r, "Логотип: файл пустой")
return
}
if len(data) > maxLogoBytes {
setupFlash(w, r, "Логотип: файл слишком большой (максимум 512 КБ)")
return
}
mime := logoMIME(header.Filename, data)
if mime == "" {
setupFlash(w, r, "Логотип: нужен файл-изображение (PNG, JPG, GIF, WEBP или SVG)")
return
}
dataURI := "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(data)
if err := h.rc.UpdateBranding(BrandingSettings{LogoDataURI: dataURI, LogoName: header.Filename}); err != nil {
setupFlash(w, r, "Логотип: ошибка сохранения: "+err.Error())
return
}
setupFlash(w, r, "Логотип обновлён: "+header.Filename+" — появится в шапке отчётов справа.")
}
// resetLogo сбрасывает логотип на встроенный по умолчанию.
func (h *setupHandlers) resetLogo(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
if err := h.rc.UpdateBranding(BrandingSettings{}); err != nil {
setupFlash(w, r, "Логотип: ошибка сброса: "+err.Error())
return
}
if builtinLogoAllowed() {
setupFlash(w, r, "Логотип сброшен на стандартный (встроенный Фонтвьель).")
return
}
setupFlash(w, r, "Логотип сброшен: в отчётах логотипа нет, пока не загрузите свой.")
}
// logoMIME определяет MIME изображения по расширению и содержимому; "" — не картинка.
func logoMIME(filename string, data []byte) string {
switch {
case strings.HasSuffix(strings.ToLower(filename), ".svg"):
return "image/svg+xml"
}
ct := http.DetectContentType(data)
head := data
if len(head) > 200 {
head = head[:200]
}
switch {
case strings.HasPrefix(ct, "image/"):
return ct
case strings.Contains(ct, "xml") && strings.Contains(strings.ToLower(string(head)), "<svg"):
return "image/svg+xml"
}
return ""
}