This commit is contained in:
Dvorinka
2025-06-20 11:36:49 +02:00
parent 78d3225b05
commit 888d1b222e
+235 -235
View File
@@ -47,7 +47,7 @@ func formatCzechTimeWithDay(t time.Time) string {
// Detect device type from User-Agent // Detect device type from User-Agent
func detectDevice(userAgent string) string { func detectDevice(userAgent string) string {
userAgent = strings.ToLower(userAgent) userAgent = strings.ToLower(userAgent)
// Mobile devices // Mobile devices
if strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipod") { if strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipod") {
return "iPhone" return "iPhone"
@@ -61,7 +61,7 @@ func detectDevice(userAgent string) string {
} else if strings.Contains(userAgent, "windows phone") { } else if strings.Contains(userAgent, "windows phone") {
return "Windows Phone" return "Windows Phone"
} }
// Desktop devices // Desktop devices
if strings.Contains(userAgent, "windows") { if strings.Contains(userAgent, "windows") {
return "Windows PC" return "Windows PC"
@@ -70,7 +70,7 @@ func detectDevice(userAgent string) string {
} else if strings.Contains(userAgent, "linux") { } else if strings.Contains(userAgent, "linux") {
return "Linux PC" return "Linux PC"
} }
// Other devices // Other devices
if strings.Contains(userAgent, "playstation") { if strings.Contains(userAgent, "playstation") {
return "PlayStation" return "PlayStation"
@@ -79,12 +79,12 @@ func detectDevice(userAgent string) string {
} else if strings.Contains(userAgent, "nintendo") { } else if strings.Contains(userAgent, "nintendo") {
return "Nintendo" return "Nintendo"
} }
// Bots and crawlers // Bots and crawlers
if strings.Contains(userAgent, "bot") || strings.Contains(userAgent, "crawler") { if strings.Contains(userAgent, "bot") || strings.Contains(userAgent, "crawler") {
return "Bot/Crawler" return "Bot/Crawler"
} }
return "Unknown Device" return "Unknown Device"
} }
@@ -100,41 +100,41 @@ type Visitor struct {
} }
type VisitorStats struct { type VisitorStats struct {
TotalVisits int `json:"total_visits"` TotalVisits int `json:"total_visits"`
TodayVisits int `json:"today_visits"` TodayVisits int `json:"today_visits"`
LastVisit time.Time `json:"last_visit"` LastVisit time.Time `json:"last_visit"`
MonthlyVisits int `json:"monthly_visits"` MonthlyVisits int `json:"monthly_visits"`
WeeklyVisits int `json:"weekly_visits"` WeeklyVisits int `json:"weekly_visits"`
LastUpdated time.Time `json:"last_updated"` LastUpdated time.Time `json:"last_updated"`
UniqueVisitors map[string]*Visitor `json:"unique_visitors"` UniqueVisitors map[string]*Visitor `json:"unique_visitors"`
MostActiveHours []struct { MostActiveHours []struct {
Hour int `json:"hour"` Hour int `json:"hour"`
Count int `json:"count"` Count int `json:"count"`
} `json:"most_active_hours"` } `json:"most_active_hours"`
MostActiveDays []struct { MostActiveDays []struct {
Day string `json:"day"` Day string `json:"day"`
Count int `json:"count"` Count int `json:"count"`
} `json:"most_active_days"` } `json:"most_active_days"`
BrowserStats map[string]int `json:"browser_stats"` BrowserStats map[string]int `json:"browser_stats"`
OSStats map[string]int `json:"os_stats"` OSStats map[string]int `json:"os_stats"`
ReferrerStats map[string]int `json:"referrer_stats"` ReferrerStats map[string]int `json:"referrer_stats"`
} }
// Format VisitorStats with Czech dates // Format VisitorStats with Czech dates
func (v *VisitorStats) FormatForDisplay() map[string]interface{} { func (v *VisitorStats) FormatForDisplay() map[string]interface{} {
displayStats := map[string]interface{}{ displayStats := map[string]interface{}{
"total_visits": v.TotalVisits, "total_visits": v.TotalVisits,
"today_visits": v.TodayVisits, "today_visits": v.TodayVisits,
"last_visit": formatCzechTimeWithDay(v.LastVisit), "last_visit": formatCzechTimeWithDay(v.LastVisit),
"monthly_visits": v.MonthlyVisits, "monthly_visits": v.MonthlyVisits,
"weekly_visits": v.WeeklyVisits, "weekly_visits": v.WeeklyVisits,
"last_updated": formatCzechTimeWithDay(v.LastUpdated), "last_updated": formatCzechTimeWithDay(v.LastUpdated),
"unique_visitors": make(map[string]interface{}), "unique_visitors": make(map[string]interface{}),
"most_active_hours": make([]map[string]interface{}, len(v.MostActiveHours)), "most_active_hours": make([]map[string]interface{}, len(v.MostActiveHours)),
"most_active_days": make([]map[string]interface{}, len(v.MostActiveDays)), "most_active_days": make([]map[string]interface{}, len(v.MostActiveDays)),
"browser_stats": v.BrowserStats, "browser_stats": v.BrowserStats,
"os_stats": v.OSStats, "os_stats": v.OSStats,
"referrer_stats": v.ReferrerStats, "referrer_stats": v.ReferrerStats,
} }
// Format unique visitors // Format unique visitors
@@ -185,11 +185,11 @@ func (v *VisitorStats) init() {
v.LastUpdated = time.Now() v.LastUpdated = time.Now()
v.UniqueVisitors = make(map[string]*Visitor) v.UniqueVisitors = make(map[string]*Visitor)
v.MostActiveHours = make([]struct { v.MostActiveHours = make([]struct {
Hour int `json:"hour"` Hour int `json:"hour"`
Count int `json:"count"` Count int `json:"count"`
}, 0) }, 0)
} }
if len(v.MostActiveDays) == 0 { if len(v.MostActiveDays) == 0 {
v.MostActiveDays = make([]struct { v.MostActiveDays = make([]struct {
Day string `json:"day"` Day string `json:"day"`
@@ -202,26 +202,26 @@ const visitorStatsFile = "data/visitor_stats.json"
func loadVisitorStats() (*VisitorStats, error) { func loadVisitorStats() (*VisitorStats, error) {
stats := &VisitorStats{ stats := &VisitorStats{
TotalVisits: 0, TotalVisits: 0,
TodayVisits: 0, TodayVisits: 0,
MonthlyVisits: 0, MonthlyVisits: 0,
WeeklyVisits: 0, WeeklyVisits: 0,
LastVisit: time.Now(), LastVisit: time.Now(),
LastUpdated: time.Now(), LastUpdated: time.Now(),
} }
// Initialize all fields // Initialize all fields
stats.init() stats.init()
data, err := os.ReadFile(visitorStatsFile) data, err := os.ReadFile(visitorStatsFile)
if err != nil { if err != nil {
return stats, nil // Return default stats if file doesn't exist return stats, nil // Return default stats if file doesn't exist
} }
if err := json.Unmarshal(data, stats); err != nil { if err := json.Unmarshal(data, stats); err != nil {
return nil, fmt.Errorf("failed to unmarshal visitor stats: %v", err) return nil, fmt.Errorf("failed to unmarshal visitor stats: %v", err)
} }
return stats, nil return stats, nil
} }
@@ -230,211 +230,211 @@ func saveVisitorStats(stats *VisitorStats) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal visitor stats: %v", err) return fmt.Errorf("failed to marshal visitor stats: %v", err)
} }
return os.WriteFile(visitorStatsFile, data, 0644) return os.WriteFile(visitorStatsFile, data, 0644)
} }
func randomString(n int) string { func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, n) bytes := make([]byte, n)
for i := range bytes { for i := range bytes {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil { if err != nil {
log.Printf("Error generating random number: %v", err) log.Printf("Error generating random number: %v", err)
return "" return ""
} }
bytes[i] = letters[num.Int64()] bytes[i] = letters[num.Int64()]
} }
return string(bytes) return string(bytes)
} }
func getVisitorId(w http.ResponseWriter, r *http.Request) string { func getVisitorId(w http.ResponseWriter, r *http.Request) string {
// Check if visitor ID is in cookie // Check if visitor ID is in cookie
cookie, err := r.Cookie("visitor_id") cookie, err := r.Cookie("visitor_id")
if err == nil && cookie.Value != "" { if err == nil && cookie.Value != "" {
return cookie.Value return cookie.Value
} }
// Generate a new unique ID // Generate a new unique ID
visitorId := fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String() + randomString(16)))) visitorId := fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String()+randomString(16))))
// Set the cookie to expire in 1 year // Set the cookie to expire in 1 year
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "visitor_id", Name: "visitor_id",
Value: visitorId, Value: visitorId,
Path: "/", Path: "/",
MaxAge: 365 * 24 * 60 * 60, // 1 year MaxAge: 365 * 24 * 60 * 60, // 1 year
HttpOnly: true, HttpOnly: true,
}) })
return visitorId return visitorId
} }
func trackVisit(w http.ResponseWriter, r *http.Request) { func trackVisit(w http.ResponseWriter, r *http.Request) {
stats, err := loadVisitorStats() stats, err := loadVisitorStats()
if err != nil { if err != nil {
log.Printf("Error loading visitor stats: %v", err) log.Printf("Error loading visitor stats: %v", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
// Initialize stats if needed // Initialize stats if needed
stats.init() stats.init()
// Get visitor info // Get visitor info
visitorId := getVisitorId(w, r) visitorId := getVisitorId(w, r)
ip := r.RemoteAddr ip := r.RemoteAddr
userAgent := r.UserAgent() userAgent := r.UserAgent()
// Detect device information // Detect device information
device := detectDevice(userAgent) device := detectDevice(userAgent)
browser := detectBrowser(userAgent) browser := detectBrowser(userAgent)
os := detectOS(userAgent) os := detectOS(userAgent)
// Update visit counts // Update visit counts
stats.TotalVisits++ stats.TotalVisits++
stats.TodayVisits++ stats.TodayVisits++
stats.LastVisit = time.Now() stats.LastVisit = time.Now()
stats.LastUpdated = time.Now() stats.LastUpdated = time.Now()
// Reset weekly visits on Monday // Reset weekly visits on Monday
if time.Now().Weekday() == time.Monday && stats.LastUpdated.Weekday() != time.Monday { if time.Now().Weekday() == time.Monday && stats.LastUpdated.Weekday() != time.Monday {
stats.WeeklyVisits = 1 stats.WeeklyVisits = 1
} else { } else {
stats.WeeklyVisits++ stats.WeeklyVisits++
} }
// Reset monthly visits at the start of the month // Reset monthly visits at the start of the month
if stats.LastUpdated.Month() != time.Now().Month() { if stats.LastUpdated.Month() != time.Now().Month() {
stats.MonthlyVisits = 1 stats.MonthlyVisits = 1
} else { } else {
stats.MonthlyVisits++ stats.MonthlyVisits++
} }
// Update unique visitors // Update unique visitors
if _, ok := stats.UniqueVisitors[visitorId]; !ok { if _, ok := stats.UniqueVisitors[visitorId]; !ok {
stats.UniqueVisitors[visitorId] = &Visitor{ stats.UniqueVisitors[visitorId] = &Visitor{
FirstVisit: time.Now(), FirstVisit: time.Now(),
LastVisit: time.Now(), LastVisit: time.Now(),
Visits: 1, Visits: 1,
IP: ip, IP: ip,
UserAgent: userAgent, UserAgent: userAgent,
Device: device, Device: device,
Browser: browser, Browser: browser,
OS: os, OS: os,
} }
} else { } else {
visitor := stats.UniqueVisitors[visitorId] visitor := stats.UniqueVisitors[visitorId]
visitor.LastVisit = time.Now() visitor.LastVisit = time.Now()
visitor.Visits++ visitor.Visits++
stats.UniqueVisitors[visitorId] = visitor stats.UniqueVisitors[visitorId] = visitor
} }
// Update device stats // Update device stats
if stats.BrowserStats == nil { if stats.BrowserStats == nil {
stats.BrowserStats = make(map[string]int) stats.BrowserStats = make(map[string]int)
} }
stats.BrowserStats[browser]++ stats.BrowserStats[browser]++
// Update OS stats // Update OS stats
if stats.OSStats == nil { if stats.OSStats == nil {
stats.OSStats = make(map[string]int) stats.OSStats = make(map[string]int)
} }
stats.OSStats[os]++ stats.OSStats[os]++
// Update device stats // Update device stats
if stats.ReferrerStats == nil { if stats.ReferrerStats == nil {
stats.ReferrerStats = make(map[string]int) stats.ReferrerStats = make(map[string]int)
} }
stats.ReferrerStats[device]++ stats.ReferrerStats[device]++
// Update referrer stats // Update referrer stats
referrer := r.Referer() referrer := r.Referer()
if referrer != "" { if referrer != "" {
if stats.ReferrerStats == nil { if stats.ReferrerStats == nil {
stats.ReferrerStats = make(map[string]int) stats.ReferrerStats = make(map[string]int)
} }
stats.ReferrerStats[referrer]++ stats.ReferrerStats[referrer]++
} }
// Update active hours // Update active hours
hour := time.Now().Hour() hour := time.Now().Hour()
found := false found := false
for i, h := range stats.MostActiveHours { for i, h := range stats.MostActiveHours {
if h.Hour == hour { if h.Hour == hour {
stats.MostActiveHours[i].Count++ stats.MostActiveHours[i].Count++
found = true found = true
break break
} }
} }
if !found { if !found {
stats.MostActiveHours = append(stats.MostActiveHours, struct { stats.MostActiveHours = append(stats.MostActiveHours, struct {
Hour int `json:"hour"` Hour int `json:"hour"`
Count int `json:"count"` Count int `json:"count"`
}{Hour: hour, Count: 1}) }{Hour: hour, Count: 1})
} }
// Update active days // Update active days
day := time.Now().Weekday().String() day := time.Now().Weekday().String()
found = false found = false
for i, d := range stats.MostActiveDays { for i, d := range stats.MostActiveDays {
if d.Day == day { if d.Day == day {
stats.MostActiveDays[i].Count++ stats.MostActiveDays[i].Count++
found = true found = true
break break
} }
} }
if !found { if !found {
stats.MostActiveDays = append(stats.MostActiveDays, struct { stats.MostActiveDays = append(stats.MostActiveDays, struct {
Day string `json:"day"` Day string `json:"day"`
Count int `json:"count"` Count int `json:"count"`
}{Day: day, Count: 1}) }{Day: day, Count: 1})
} }
// Save visitor stats // Save visitor stats
if err := saveVisitorStats(stats); err != nil { if err := saveVisitorStats(stats); err != nil {
log.Printf("Error saving visitor stats: %v", err) log.Printf("Error saving visitor stats: %v", err)
} }
} }
// Helper function to extract browser from User-Agent // Helper function to extract browser from User-Agent
func detectBrowser(userAgent string) string { func detectBrowser(userAgent string) string {
userAgent = strings.ToLower(userAgent) userAgent = strings.ToLower(userAgent)
if strings.Contains(userAgent, "chrome") { if strings.Contains(userAgent, "chrome") {
return "Chrome" return "Chrome"
} else if strings.Contains(userAgent, "safari") && !strings.Contains(userAgent, "chrome") { } else if strings.Contains(userAgent, "safari") && !strings.Contains(userAgent, "chrome") {
return "Safari" return "Safari"
} else if strings.Contains(userAgent, "firefox") { } else if strings.Contains(userAgent, "firefox") {
return "Firefox" return "Firefox"
} else if strings.Contains(userAgent, "msie") || strings.Contains(userAgent, "trident") { } else if strings.Contains(userAgent, "msie") || strings.Contains(userAgent, "trident") {
return "Internet Explorer" return "Internet Explorer"
} else if strings.Contains(userAgent, "edge") { } else if strings.Contains(userAgent, "edge") {
return "Edge" return "Edge"
} else if strings.Contains(userAgent, "opera") { } else if strings.Contains(userAgent, "opera") {
return "Opera" return "Opera"
} else { } else {
return "Unknown" return "Unknown"
} }
} }
// Helper function to extract OS from User-Agent // Helper function to extract OS from User-Agent
func detectOS(userAgent string) string { func detectOS(userAgent string) string {
userAgent = strings.ToLower(userAgent) userAgent = strings.ToLower(userAgent)
if strings.Contains(userAgent, "windows") { if strings.Contains(userAgent, "windows") {
return "Windows" return "Windows"
} else if strings.Contains(userAgent, "mac os") || strings.Contains(userAgent, "macintosh") { } else if strings.Contains(userAgent, "mac os") || strings.Contains(userAgent, "macintosh") {
return "macOS" return "macOS"
} else if strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipad") || strings.Contains(userAgent, "ipod") { } else if strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipad") || strings.Contains(userAgent, "ipod") {
return "iOS" return "iOS"
} else if strings.Contains(userAgent, "android") { } else if strings.Contains(userAgent, "android") {
return "Android" return "Android"
} else if strings.Contains(userAgent, "linux") { } else if strings.Contains(userAgent, "linux") {
return "Linux" return "Linux"
} else if strings.Contains(userAgent, "bsd") { } else if strings.Contains(userAgent, "bsd") {
return "BSD" return "BSD"
} else { } else {
return "Unknown" return "Unknown"
} }
} }
// Helper function to extract OS from User-Agent // Helper function to extract OS from User-Agent
@@ -450,7 +450,7 @@ func getVisitorStats(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(stats); err != nil { if err := json.NewEncoder(w).Encode(stats); err != nil {
log.Printf("Error encoding visitor stats: %v", err) log.Printf("Error encoding visitor stats: %v", err)
@@ -524,9 +524,9 @@ func main() {
r := mux.NewRouter() r := mux.NewRouter()
// Visitor tracking endpoints // Visitor tracking endpoints
r.HandleFunc("/api/track-visit", trackVisit).Methods("GET") r.HandleFunc("/api/track-visit", trackVisit).Methods("GET")
r.HandleFunc("/api/visitor-stats", getVisitorStats).Methods("GET") r.HandleFunc("/api/visitor-stats", getVisitorStats).Methods("GET")
// Set up reverse proxy to kontakt service // Set up reverse proxy to kontakt service
kontaktURL, _ := url.Parse("http://webportal:8080") kontaktURL, _ := url.Parse("http://webportal:8080")
@@ -1423,11 +1423,11 @@ func handleSubmit(w http.ResponseWriter, r *http.Request) {
} }
func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechMonths []string) error { func sendEmail(entry TripEntry, parsedDateStart, parsedDateEnd time.Time, czechMonths []string) error {
smtpHost := "mail.pp-kunovice.cz" smtpHost := "smtp.purelymail.com"
smtpPort := 465 smtpPort := 465
sender := "sluzebnicek@pp-kunovice.cz" sender := "info@tdvorak.dev"
password := "7g}qznB5bj" password := "%8s3Yad*!b3*t"
recipient := "sluzebnicek@pp-kunovice.cz" recipient := "info@tdvorak.dev"
m := gomail.NewMessage() m := gomail.NewMessage()
m.SetHeader("From", sender) m.SetHeader("From", sender)