Files
zuevav da12a9ec1c feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk)
- трансферные таблицы FTV_M2M: создание при установке, события, справочник участников
- сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO
- боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК
- онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML)
- модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 19:41:12 +03:00

92 lines
3.3 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
}
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 ""
}