This commit is contained in:
Tomas Dvorak
2025-10-29 21:20:16 +01:00
parent 823fabee02
commit 16e4533202
61 changed files with 2308 additions and 942 deletions
+272 -18
View File
@@ -2266,8 +2266,6 @@ func (bc *BaseController) PatchTeamLogoOverride(c *gin.Context) {
c.JSON(http.StatusOK, item)
}
// ProxyImage streams a remote image to the client to avoid browser CORS restrictions for Canvas operations
// GET /api/v1/proxy/image?url=<remote_image_url>
func (bc *BaseController) ProxyImage(c *gin.Context) {
raw := c.Query("url")
if raw == "" {
@@ -2287,8 +2285,15 @@ func (bc *BaseController) ProxyImage(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "request init failed"})
return
}
// Some CDNs require a UA
req.Header.Set("User-Agent", "fotbal-club/1.0 (+https://localhost)")
// Use realistic browser headers - some CDNs block unknown clients
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36")
req.Header.Set("Accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
req.Header.Set("Accept-Language", "cs-CZ,cs;q=0.9,en;q=0.8")
// Set a benign referer tied to the target host to satisfy anti-hotlink checks
if u.Host != "" {
ref := u.Scheme + "://" + u.Host + "/"
req.Header.Set("Referer", ref)
}
resp, err := client.Do(req)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": "fetch failed"})
@@ -2366,6 +2371,10 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
ClubLogoURL string `json:"club_logo_url"`
ClubURL string `json:"club_url"`
// Optional bases
FrontendBaseURL string `json:"frontend_base_url"`
APIBaseURL string `json:"api_base_url"`
// Social profiles (optional)
FacebookURL string `json:"facebook_url"`
InstagramURL string `json:"instagram_url"`
@@ -2473,6 +2482,45 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
if body.FrontpageStyle != "" {
s.FrontpageStyle = body.FrontpageStyle
}
// Detect and persist base URLs
if v := strings.TrimSpace(body.APIBaseURL); v != "" {
s.APIBaseURL = v
}
if v := strings.TrimSpace(body.FrontendBaseURL); v != "" {
s.FrontendBaseURL = v
}
// If not provided, infer from current request and proxy headers
{
scheme := "http"
if c.Request.TLS != nil || strings.EqualFold(c.Request.Header.Get("X-Forwarded-Proto"), "https") || strings.Contains(strings.ToLower(c.Request.Header.Get("CF-Visitor")), "https") {
scheme = "https"
}
host := c.Request.Host
if xf := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Host")); xf != "" {
parts := strings.Split(xf, ",")
if len(parts) > 0 {
if h := strings.TrimSpace(parts[0]); h != "" { host = h }
}
}
if !strings.Contains(host, ":") {
if xfp := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Port")); xfp != "" {
if (scheme == "http" && xfp != "80") || (scheme == "https" && xfp != "443") {
host = host + ":" + xfp
}
}
}
if strings.TrimSpace(s.APIBaseURL) == "" {
s.APIBaseURL = scheme + "://" + host + "/api/v1"
}
if strings.TrimSpace(s.FrontendBaseURL) == "" {
if origin := strings.TrimSpace(c.Request.Header.Get("Origin")); origin != "" {
s.FrontendBaseURL = origin
} else {
s.FrontendBaseURL = scheme + "://" + host
}
}
}
// SMTP overrides from initial setup
if body.SMTP != nil {
if v := strings.TrimSpace(body.SMTP.Host); v != "" {
@@ -2594,7 +2642,11 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
_ = os.WriteFile(tmp, b, 0o644)
_ = os.Rename(tmp, outPath)
}(s)
go services.PrefetchOnce(getPrefetchBaseURL())
{
base := strings.TrimSpace(s.APIBaseURL)
if base == "" { base = getPrefetchBaseURL() }
go services.PrefetchOnce(strings.TrimRight(base, "/"))
}
if strings.TrimSpace(s.YoutubeURL) != "" {
go func(u string) { _ = services.RefreshYouTubeChannelNow(u) }(s.YoutubeURL)
}
@@ -2603,7 +2655,7 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
go func(link string) { _ = services.RefreshZoneramaNow(link) }(g)
}
c.JSON(http.StatusOK, gin.H{"message": "Inicializace již byla provedena"})
c.JSON(http.StatusOK, gin.H{"message": "Inicializace již byla provedena", "frontend_base_url": s.FrontendBaseURL, "api_base_url": s.APIBaseURL})
return
}
@@ -2666,6 +2718,10 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
From string `json:"from"`
UseTLS *bool `json:"use_tls"`
} `json:"smtp"`
// Optional bases
FrontendBaseURL string `json:"frontend_base_url"`
APIBaseURL string `json:"api_base_url"`
}
var body reqBody
if err := c.ShouldBindJSON(&body); err != nil {
@@ -2806,6 +2862,44 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
if body.FrontpageStyle != "" {
s.FrontpageStyle = body.FrontpageStyle
}
// Persist base URLs (prefer request body, otherwise infer)
if v := strings.TrimSpace(body.APIBaseURL); v != "" {
s.APIBaseURL = v
}
if v := strings.TrimSpace(body.FrontendBaseURL); v != "" {
s.FrontendBaseURL = v
}
{
scheme := "http"
if c.Request.TLS != nil || strings.EqualFold(c.Request.Header.Get("X-Forwarded-Proto"), "https") || strings.Contains(strings.ToLower(c.Request.Header.Get("CF-Visitor")), "https") {
scheme = "https"
}
host := c.Request.Host
if xf := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Host")); xf != "" {
parts := strings.Split(xf, ",")
if len(parts) > 0 {
if h := strings.TrimSpace(parts[0]); h != "" { host = h }
}
}
if !strings.Contains(host, ":") {
if xfp := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Port")); xfp != "" {
if (scheme == "http" && xfp != "80") || (scheme == "https" && xfp != "443") {
host = host + ":" + xfp
}
}
}
if strings.TrimSpace(s.APIBaseURL) == "" {
s.APIBaseURL = scheme + "://" + host + "/api/v1"
}
if strings.TrimSpace(s.FrontendBaseURL) == "" {
if origin := strings.TrimSpace(c.Request.Header.Get("Origin")); origin != "" {
s.FrontendBaseURL = origin
} else {
s.FrontendBaseURL = scheme + "://" + host
}
}
}
// SMTP overrides from initial setup
if body.SMTP != nil {
if v := strings.TrimSpace(body.SMTP.Host); v != "" {
@@ -2940,12 +3034,13 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
logger.Info("Starting initial data prefetch and setup operations in background...")
// Run all setup operations in a single background goroutine
go func(settingsID uint, youtubeURL, galleryURL, adminEmail string) {
go func(settingsID uint, youtubeURL, galleryURL, adminEmail string, apiBase string) {
defer func() { _ = recover() }()
// 1. Trigger prefetch (matches, standings, etc.)
baseURL := getPrefetchBaseURL()
services.PrefetchOnce(baseURL)
baseURL := strings.TrimSpace(apiBase)
if baseURL == "" { baseURL = getPrefetchBaseURL() }
services.PrefetchOnce(strings.TrimRight(baseURL, "/"))
logger.Info("Background prefetch completed")
// Auto-populate competition aliases from FACR data
@@ -2984,10 +3079,10 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
}
logger.Info("All background setup operations completed")
}(s.ID, s.YoutubeURL, s.GalleryURL, admin.Email)
}(s.ID, s.YoutubeURL, s.GalleryURL, admin.Email, s.APIBaseURL)
logger.Info("SetupInitialize finished successfully - background operations running")
c.JSON(http.StatusOK, gin.H{"message": "Setup completed successfully"})
c.JSON(http.StatusOK, gin.H{"message": "Setup completed successfully", "frontend_base_url": s.FrontendBaseURL, "api_base_url": s.APIBaseURL})
}
// UpdateSettings updates settings (upsert singleton)
@@ -3103,6 +3198,10 @@ func (bc *BaseController) UpdateSettings(c *gin.Context) {
// Homepage matches display configuration
FinishedMatchDisplayDays *int `json:"finished_match_display_days"`
// Deployment base URLs (optional, for domain/IP change)
FrontendBaseURL *string `json:"frontend_base_url"`
APIBaseURL *string `json:"api_base_url"`
}
var body reqBody
if err := c.ShouldBindJSON(&body); err != nil {
@@ -3404,6 +3503,14 @@ func (bc *BaseController) UpdateSettings(c *gin.Context) {
s.FinishedMatchDisplayDays = *body.FinishedMatchDisplayDays
}
// Deployment base URLs
if body.FrontendBaseURL != nil {
s.FrontendBaseURL = strings.TrimSpace(*body.FrontendBaseURL)
}
if body.APIBaseURL != nil {
s.APIBaseURL = strings.TrimSpace(*body.APIBaseURL)
}
if s.ID == 0 {
if err := bc.DB.Create(&s).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"chyba": "Nelze uložit nastavení"})
@@ -3425,10 +3532,11 @@ func (bc *BaseController) UpdateSettings(c *gin.Context) {
}
logger.Info("UpdateSettings saved: club_id=%s club_name=%s gallery_url=%s gallery_label=%s", s.ClubID, s.ClubName, s.GalleryURL, s.GalleryLabel)
// Best-effort: trigger prefetch so cached settings.json and dependent files update immediately
go func() {
base := getPrefetchBaseURL()
services.PrefetchOnce(base)
}()
go func(urlFromSettings string) {
base := strings.TrimSpace(urlFromSettings)
if base == "" { base = getPrefetchBaseURL() }
services.PrefetchOnce(strings.TrimRight(base, "/"))
}(s.APIBaseURL)
// If gallery_url is a Zonerama link, refresh Zonerama cache immediately
if g := strings.TrimSpace(s.GalleryURL); g != "" && strings.Contains(strings.ToLower(g), "zonerama.com") {
go func(link string) { _ = services.RefreshZoneramaNow(link) }(g)
@@ -3569,6 +3677,9 @@ func (bc *BaseController) GetPublicSettings(c *gin.Context) {
"map_zoom_level": s.MapZoomLevel,
"map_style": s.MapStyle,
"show_map_on_homepage": s.ShowMapOnHomepage,
// Deployment base URLs (hints for frontend tooling)
"frontend_base_url": s.FrontendBaseURL,
"api_base_url": s.APIBaseURL,
}
logger.Debug("GetPublicSettings response includes gallery: url=%s label=%s", s.GalleryURL, s.GalleryLabel)
c.JSON(http.StatusOK, resp)
@@ -4087,6 +4198,127 @@ func (bc *BaseController) DeleteSponsor(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"zprava": "Sponzor byl smazán"})
}
// Banners (separate from sponsors)
func (bc *BaseController) GetBanners(c *gin.Context) {
var items []models.Banner
q := bc.DB.Model(&models.Banner{})
activeOnly := strings.ToLower(strings.TrimSpace(c.DefaultQuery("active", "true"))) != "false"
if activeOnly {
q = q.Where("is_active = ?", true)
}
if p := strings.TrimSpace(c.Query("placement")); p != "" {
q = q.Where("placement = ?", p)
}
if err := q.Order("display_order ASC, created_at ASC").Find(&items).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"chyba": "Chyba databáze"})
return
}
c.JSON(http.StatusOK, items)
}
func (bc *BaseController) CreateBanner(c *gin.Context) {
var body struct {
Name string `json:"name" binding:"required"`
ImageURL string `json:"image_url"`
ClickURL string `json:"click_url"`
Placement string `json:"placement"`
Width *int `json:"width"`
Height *int `json:"height"`
IsActive *bool `json:"is_active"`
DisplayOrder *int `json:"display_order"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"chyba": "Neplatná data", "detail": err.Error()})
return
}
name := strings.TrimSpace(body.Name)
if name == "" {
c.JSON(http.StatusBadRequest, gin.H{"chyba": "Název banneru je povinný"})
return
}
item := models.Banner{
Name: name,
ImageURL: strings.TrimSpace(body.ImageURL),
ClickURL: strings.TrimSpace(body.ClickURL),
Placement: strings.TrimSpace(body.Placement),
IsActive: true,
}
if body.Width != nil { item.Width = *body.Width }
if body.Height != nil { item.Height = *body.Height }
if body.DisplayOrder != nil { item.DisplayOrder = *body.DisplayOrder }
if body.IsActive != nil { item.IsActive = *body.IsActive }
if err := bc.DB.Create(&item).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"chyba": "Nelze vytvořit banner"})
return
}
c.JSON(http.StatusCreated, item)
}
func (bc *BaseController) UpdateBanner(c *gin.Context) {
id := c.Param("id")
var item models.Banner
if err := bc.DB.First(&item, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"chyba": "Banner nenalezen"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"chyba": "Chyba databáze"})
return
}
var body struct {
Name *string `json:"name"`
ImageURL *string `json:"image_url"`
ClickURL *string `json:"click_url"`
Placement *string `json:"placement"`
Width *int `json:"width"`
Height *int `json:"height"`
IsActive *bool `json:"is_active"`
DisplayOrder *int `json:"display_order"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"chyba": "Neplatná data", "detail": err.Error()})
return
}
if body.Name != nil {
v := strings.TrimSpace(*body.Name)
if v == "" {
c.JSON(http.StatusBadRequest, gin.H{"chyba": "Název banneru nemůže být prázdný"})
return
}
item.Name = v
}
if body.ImageURL != nil { item.ImageURL = strings.TrimSpace(*body.ImageURL) }
if body.ClickURL != nil { item.ClickURL = strings.TrimSpace(*body.ClickURL) }
if body.Placement != nil { item.Placement = strings.TrimSpace(*body.Placement) }
if body.Width != nil { item.Width = *body.Width }
if body.Height != nil { item.Height = *body.Height }
if body.IsActive != nil { item.IsActive = *body.IsActive }
if body.DisplayOrder != nil { item.DisplayOrder = *body.DisplayOrder }
if err := bc.DB.Save(&item).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"chyba": "Nelze aktualizovat banner"})
return
}
c.JSON(http.StatusOK, item)
}
func (bc *BaseController) DeleteBanner(c *gin.Context) {
id := c.Param("id")
var item models.Banner
if err := bc.DB.First(&item, id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"chyba": "Banner nenalezen"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"chyba": "Chyba databáze"})
return
}
if err := bc.DB.Delete(&item).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"chyba": "Nelze smazat banner"})
return
}
c.JSON(http.StatusOK, gin.H{"zprava": "Banner byl smazán"})
}
func (bc *BaseController) UploadImage(c *gin.Context) {
f, err := c.FormFile("file")
if err != nil {
@@ -4150,15 +4382,37 @@ func (bc *BaseController) UploadImage(c *gin.Context) {
// Build absolute URL from request (supports proxies)
scheme := "http"
if c.Request.TLS != nil || strings.EqualFold(c.Request.Header.Get("X-Forwarded-Proto"), "https") {
if c.Request.TLS != nil || strings.EqualFold(c.Request.Header.Get("X-Forwarded-Proto"), "https") || strings.Contains(strings.ToLower(c.Request.Header.Get("CF-Visitor")), "https") {
scheme = "https"
}
host := c.Request.Host
if xf := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Host")); xf != "" {
host = xf
// Take the first value if comma-separated
parts := strings.Split(xf, ",")
if len(parts) > 0 {
h := strings.TrimSpace(parts[0])
if h != "" { host = h }
}
}
// Append forwarded port when host has no explicit port and it's non-default
if !strings.Contains(host, ":") {
if xfp := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Port")); xfp != "" {
if (scheme == "http" && xfp != "80") || (scheme == "https" && xfp != "443") {
host = host + ":" + xfp
}
}
}
absolute := scheme + "://" + host + urlPath
c.JSON(http.StatusOK, gin.H{"url": absolute})
c.JSON(http.StatusOK, gin.H{
// Always return a backend-relative path for storage
"url": urlPath,
// Convenience absolute URL for immediate usage in UIs
"absolute_url": absolute,
// Basic metadata (best-effort)
"name": outName,
"type": mimeType,
"size": f.Size,
})
}
// Global newsletter automation instance (set from main)
+83 -25
View File
@@ -1099,15 +1099,15 @@ func (cc *ContactController) ForwardContactMessage(c *gin.Context) {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Message not found"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch message"})
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
}
return
}
// Prepare email data for forwarding
// Prepare email data for forwarding (Czech subject)
forwardData := &email.EmailData{
Subject: fmt.Sprintf("Fwd: Contact Form - %s", message.Subject),
To: []string{input.ToEmail},
Subject: fmt.Sprintf("Přeposláno: Kontaktní formulář - %s", message.Subject),
To: []string{input.ToEmail},
Template: "contact_form",
Data: struct {
Name string
@@ -1128,26 +1128,21 @@ func (cc *ContactController) ForwardContactMessage(c *gin.Context) {
},
}
// Send email asynchronously
go func() {
if err := cc.emailService.SendEmail(forwardData); err != nil {
logger.Error("Failed to forward contact message %d to %s: %v", id, input.ToEmail, err)
} else {
logger.Info("Contact message %d forwarded to %s", id, input.ToEmail)
}
}()
c.JSON(http.StatusOK, gin.H{"message": "Message is being forwarded to " + input.ToEmail})
if err := cc.emailService.SendEmail(forwardData); err != nil {
logger.Error("Failed to forward contact message %d to %s: %v", message.ID, input.ToEmail, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to forward message"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Message forwarded"})
}
// ForwardAllContactMessages forwards all contact messages to a specified email (admin only)
// @Summary Forward all contact messages
// @Description Forwards all contact messages to a specified email address (admin only)
// @Tags admin
// @Security Bearer
// @Accept json
// @Produce json
// @Param input body map[string]string true "{ to_email: string }"
// @Param input body map[string]string true "{ to_email: string, to_emails: []string, save_default: bool }"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 401 {object} map[string]string
@@ -1160,13 +1155,76 @@ func (cc *ContactController) ForwardAllContactMessages(c *gin.Context) {
}
var input struct {
ToEmail string `json:"to_email" binding:"required,email"`
ToEmail string `json:"to_email"`
ToEmails []string `json:"to_emails"`
SaveDefault bool `json:"save_default"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Valid email address is required"})
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid payload"})
return
}
// Build recipients list (supports comma/semicolon/space separated string or array)
recipients := make([]string, 0)
add := func(s string) {
v := strings.TrimSpace(s)
if v != "" {
recipients = append(recipients, v)
}
}
if len(input.ToEmails) > 0 {
for _, e := range input.ToEmails {
add(e)
}
}
if input.ToEmail != "" {
// split by common separators to allow multiple addresses in a single string
parts := strings.FieldsFunc(input.ToEmail, func(r rune) bool { return r == ',' || r == ';' || r == ' ' || r == '\n' || r == '\t' })
if len(parts) > 1 {
for _, p := range parts {
add(p)
}
} else {
add(input.ToEmail)
}
}
// Deduplicate
if len(recipients) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "No recipient email provided"})
return
}
uniq := make(map[string]struct{})
out := make([]string, 0, len(recipients))
for _, e := range recipients {
v := strings.TrimSpace(strings.ToLower(e))
if v == "" {
continue
}
if _, ok := uniq[v]; ok {
continue
}
uniq[v] = struct{}{}
out = append(out, e)
}
recipients = out
// Optionally save as default auto-forward list in Settings
if input.SaveDefault {
var set models.Settings
if err := cc.DB.First(&set).Error; err != nil {
if err == gorm.ErrRecordNotFound {
set = models.Settings{}
set.ContactForwardEnabled = true
set.ContactForwardList = strings.Join(recipients, ", ")
_ = cc.DB.Create(&set).Error
}
} else {
set.ContactForwardEnabled = true
set.ContactForwardList = strings.Join(recipients, ", ")
_ = cc.DB.Save(&set).Error
}
}
// Fetch all messages
var messages []models.ContactMessage
if err := cc.DB.Order("created_at DESC").Find(&messages).Error; err != nil {
@@ -1180,12 +1238,12 @@ func (cc *ContactController) ForwardAllContactMessages(c *gin.Context) {
}
// Forward all messages asynchronously
go func(msgs []models.ContactMessage, toEmail string) {
go func(msgs []models.ContactMessage, dest []string) {
successCount := 0
for _, message := range msgs {
forwardData := &email.EmailData{
Subject: fmt.Sprintf("Fwd: Contact Form - %s", message.Subject),
To: []string{toEmail},
Subject: fmt.Sprintf("Přeposláno: Kontaktní formulář - %s", message.Subject),
To: dest,
Template: "contact_form",
Data: struct {
Name string
@@ -1207,16 +1265,16 @@ func (cc *ContactController) ForwardAllContactMessages(c *gin.Context) {
}
if err := cc.emailService.SendEmail(forwardData); err != nil {
logger.Error("Failed to forward contact message %d to %s: %v", message.ID, toEmail, err)
logger.Error("Failed to forward contact message %d to %v: %v", message.ID, dest, err)
} else {
successCount++
}
}
logger.Info("Forwarded %d of %d contact messages to %s", successCount, len(msgs), toEmail)
}(messages, input.ToEmail)
logger.Info("Forwarded %d of %d contact messages to %v", successCount, len(msgs), dest)
}(messages, recipients)
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Forwarding %d message(s) to %s", len(messages), input.ToEmail),
"message": fmt.Sprintf("Přeposílám %d zpráv na: %s", len(messages), strings.Join(recipients, ", ")),
"count": len(messages),
})
}
@@ -159,8 +159,9 @@ func (ctrl *ImageProcessingController) ProcessImage(c *gin.Context) {
}
absolute := scheme + "://" + host + outputPath
c.JSON(http.StatusOK, gin.H{
"url": absolute,
"format": format,
"url": outputPath,
"absolute_url": absolute,
"format": format,
})
}
@@ -346,7 +347,8 @@ func (ctrl *ImageProcessingController) CropAndUpload(c *gin.Context) {
}
absolute := scheme + "://" + host + outputPath
c.JSON(http.StatusOK, gin.H{
"url": absolute,
"url": outputPath,
"absolute_url": absolute,
})
}
@@ -431,7 +433,28 @@ func (ctrl *ImageProcessingController) QuickEdit(c *gin.Context) {
return
}
scheme := "http"
if c.Request.TLS != nil || strings.EqualFold(c.Request.Header.Get("X-Forwarded-Proto"), "https") {
scheme = "https"
}
host := c.Request.Host
if xf := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Host")); xf != "" {
parts := strings.Split(xf, ",")
if len(parts) > 0 {
h := strings.TrimSpace(parts[0])
if h != "" { host = h }
}
}
if !strings.Contains(host, ":") {
if xfp := strings.TrimSpace(c.Request.Header.Get("X-Forwarded-Port")); xfp != "" {
if (scheme == "http" && xfp != "80") || (scheme == "https" && xfp != "443") {
host = host + ":" + xfp
}
}
}
absolute := scheme + "://" + host + outputPath
c.JSON(http.StatusOK, gin.H{
"url": outputPath,
"url": outputPath,
"absolute_url": absolute,
})
}