mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
221 lines
5.7 KiB
Go
221 lines
5.7 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
_ "image/jpeg"
|
|
_ "image/png"
|
|
"math"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"fotbal-club/internal/models"
|
|
|
|
"github.com/muesli/clusters"
|
|
"github.com/muesli/kmeans"
|
|
)
|
|
|
|
type FACRService struct {
|
|
baseURL string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
type FACRClubSearchResponse struct {
|
|
Query string `json:"query"`
|
|
Count int `json:"count"`
|
|
Results []struct {
|
|
Name string `json:"name"`
|
|
ClubID string `json:"club_id"`
|
|
ClubType string `json:"club_type"`
|
|
LogoURL string `json:"logo_url"`
|
|
Category string `json:"category"`
|
|
} `json:"results"`
|
|
}
|
|
|
|
// NewFACRService creates a new FACR service instance
|
|
func NewFACRService(baseURL string) *FACRService {
|
|
return &FACRService{
|
|
baseURL: strings.TrimSuffix(baseURL, "/"),
|
|
httpClient: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// SearchClubs searches for clubs by name
|
|
func (s *FACRService) SearchClubs(query string) ([]models.ClubSearchResult, error) {
|
|
if query == "" {
|
|
return nil, errors.New("search query cannot be empty")
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/club/search?q=%s", s.baseURL, query)
|
|
resp, err := s.httpClient.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error making request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
}
|
|
|
|
var result FACRClubSearchResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, fmt.Errorf("error decoding response: %v", err)
|
|
}
|
|
|
|
var clubs []models.ClubSearchResult
|
|
for _, club := range result.Results {
|
|
clubs = append(clubs, models.ClubSearchResult{
|
|
ID: club.ClubID,
|
|
Name: club.Name,
|
|
Type: club.ClubType,
|
|
LogoURL: club.LogoURL,
|
|
Category: club.Category,
|
|
})
|
|
}
|
|
|
|
return clubs, nil
|
|
}
|
|
|
|
// extractColorsFromLogo extracts dominant colors from a logo image
|
|
func (s *FACRService) extractColorsFromLogo(logoURL string) (string, string, string, error) {
|
|
resp, err := s.httpClient.Get(logoURL)
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("error downloading logo: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
img, _, err := image.Decode(resp.Body)
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("error decoding image: %v", err)
|
|
}
|
|
|
|
bounds := img.Bounds()
|
|
var points []clusters.Observation
|
|
|
|
// Sample pixels (every 10th pixel for performance)
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y += 10 {
|
|
for x := bounds.Min.X; x < bounds.Max.X; x += 10 {
|
|
r, g, b, a := img.At(x, y).RGBA()
|
|
if a > 0x7FFF { // Skip transparent pixels
|
|
// Convert to float64 slice and create a new observation
|
|
point := []float64{
|
|
float64(r >> 8),
|
|
float64(g >> 8),
|
|
float64(b >> 8),
|
|
}
|
|
points = append(points, clusters.Coordinates(point))
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(points) == 0 {
|
|
return "#1E40AF", "#F59E0B", "#1F2937", nil
|
|
}
|
|
|
|
// Find 3 dominant colors using k-means
|
|
km := kmeans.New()
|
|
clusters, err := km.Partition(points, 3) // Find 3 dominant colors
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("error in k-means clustering: %v", err)
|
|
}
|
|
|
|
// Sort clusters by size (number of points in each cluster)
|
|
for i := 0; i < len(clusters); i++ {
|
|
for j := i + 1; j < len(clusters); j++ {
|
|
if len(clusters[j].Observations) > len(clusters[i].Observations) {
|
|
clusters[i], clusters[j] = clusters[j], clusters[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get primary and secondary colors
|
|
primary := clusters[0].Center
|
|
secondary := clusters[1%len(clusters)].Center
|
|
|
|
// Calculate text color based on brightness
|
|
brightness := (0.299*primary[0] + 0.587*primary[1] + 0.114*primary[2]) / 255.0
|
|
textColor := "#000000"
|
|
if brightness < 0.5 {
|
|
textColor = "#FFFFFF"
|
|
}
|
|
|
|
return rgbToHex(primary), rgbToHex(secondary), textColor, nil
|
|
}
|
|
|
|
// rgbToHex converts RGB values to hex color
|
|
func rgbToHex(rgb []float64) string {
|
|
return fmt.Sprintf("#%02X%02X%02X",
|
|
uint8(math.Round(rgb[0])),
|
|
uint8(math.Round(rgb[1])),
|
|
uint8(math.Round(rgb[2])),
|
|
)
|
|
}
|
|
|
|
// GetClubDetails fetches detailed information about a club
|
|
func (s *FACRService) GetClubDetails(clubID string) (*models.ClubInfo, error) {
|
|
if clubID == "" {
|
|
return nil, errors.New("club ID cannot be empty")
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/club/football/%s", s.baseURL, clubID)
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating request: %v", err)
|
|
}
|
|
|
|
resp, err := s.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error making request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
}
|
|
|
|
var club struct {
|
|
Name string `json:"name"`
|
|
ClubID string `json:"club_id"`
|
|
LogoURL string `json:"logo_url"`
|
|
Category string `json:"category"`
|
|
}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&club); err != nil {
|
|
return nil, fmt.Errorf("error decoding response: %v", err)
|
|
}
|
|
|
|
// Extract colors from logo if available
|
|
var primaryColor, secondaryColor, textColor string
|
|
var errExtract error
|
|
|
|
if club.LogoURL != "" {
|
|
primaryColor, secondaryColor, textColor, errExtract = s.extractColorsFromLogo(club.LogoURL)
|
|
if errExtract != nil {
|
|
// Log the error but continue with default colors
|
|
fmt.Printf("Warning: Could not extract colors from logo: %v\n", errExtract)
|
|
}
|
|
}
|
|
|
|
// Set default colors if extraction failed or wasn't attempted
|
|
if primaryColor == "" {
|
|
primaryColor = "#1E40AF" // Blue
|
|
secondaryColor = "#F59E0B" // Amber
|
|
textColor = "#1F2937" // Gray-800
|
|
}
|
|
|
|
return &models.ClubInfo{
|
|
FACRClubID: club.ClubID,
|
|
Name: club.Name,
|
|
ShortName: club.Name, // Using name as fallback for short name
|
|
LogoURL: club.LogoURL,
|
|
PrimaryColor: primaryColor,
|
|
SecondaryColor: secondaryColor,
|
|
TextColor: textColor,
|
|
}, nil
|
|
}
|