mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #100 - WE ARE FUCKING DONE, hotfixes incoming but we did it in 100 days, lets fucking go guys, anyone reading this...i love you
This commit is contained in:
+58
-59
@@ -88,17 +88,17 @@ type Article struct {
|
||||
OGImageURL string `json:"og_image_url"`
|
||||
// Optional: link to external content or embedded media
|
||||
ExternalLink string `json:"external_link"`
|
||||
ViewCount int `gorm:"default:0;index" json:"view_count"`
|
||||
ReadTime int `gorm:"default:0" json:"read_time"` // estimated reading time in minutes
|
||||
UniqueViews int `gorm:"default:0" json:"unique_views"` // Unique visitors (tracked by IP/session)
|
||||
ViewCount int `gorm:"default:0;index" json:"view_count"`
|
||||
ReadTime int `gorm:"default:0" json:"read_time"` // estimated reading time in minutes
|
||||
UniqueViews int `gorm:"default:0" json:"unique_views"` // Unique visitors (tracked by IP/session)
|
||||
// Store the category name directly to simplify queries (denormalized)
|
||||
CategoryName string `json:"category_name"`
|
||||
Attachments string `gorm:"type:text" json:"attachments"` // JSON array: ["url1", "url2", ...]
|
||||
Attachments string `gorm:"type:text" json:"attachments"` // JSON array: ["url1", "url2", ...]
|
||||
// Gallery association (optional)
|
||||
GalleryAlbumID string `json:"gallery_album_id"`
|
||||
GalleryAlbumURL string `json:"gallery_album_url"`
|
||||
GalleryAlbumID string `json:"gallery_album_id"`
|
||||
GalleryAlbumURL string `json:"gallery_album_url"`
|
||||
// Stored as JSON string or comma-separated list; frontend normalizes
|
||||
GalleryPhotoIDs string `gorm:"type:text" json:"gallery_photo_ids"`
|
||||
GalleryPhotoIDs string `gorm:"type:text" json:"gallery_photo_ids"`
|
||||
// YouTube video association (optional)
|
||||
YouTubeVideoID string `json:"youtube_video_id"`
|
||||
YouTubeVideoTitle string `gorm:"type:text" json:"youtube_video_title"`
|
||||
@@ -108,10 +108,10 @@ type Article struct {
|
||||
// Removed omitempty to always include in JSON (even if null)
|
||||
MatchLink *ArticleMatchLink `gorm:"-" json:"match_link"`
|
||||
// Computed helpers (not persisted)
|
||||
CategorySlug string `gorm:"-" json:"category_slug,omitempty"`
|
||||
CompetitionAlias string `gorm:"-" json:"competition_alias,omitempty"`
|
||||
NormalizedCategory string `gorm:"-" json:"normalized_category,omitempty"`
|
||||
URL string `gorm:"-" json:"url,omitempty"`
|
||||
CategorySlug string `gorm:"-" json:"category_slug,omitempty"`
|
||||
CompetitionAlias string `gorm:"-" json:"competition_alias,omitempty"`
|
||||
NormalizedCategory string `gorm:"-" json:"normalized_category,omitempty"`
|
||||
URL string `gorm:"-" json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// ArticleTeamLink represents a link from an article to a team identified by an external FACR ID
|
||||
@@ -143,7 +143,7 @@ type Team struct {
|
||||
ShortName string
|
||||
Description string
|
||||
LogoURL string `json:"logo_url"`
|
||||
IsActive bool `gorm:"default:true"`
|
||||
IsActive bool `gorm:"default:true"`
|
||||
}
|
||||
|
||||
// Player represents a football player
|
||||
@@ -184,15 +184,15 @@ type Sponsor struct {
|
||||
|
||||
// VideoTitleOverride represents a per-video title override (for auto YouTube source)
|
||||
type VideoTitleOverride struct {
|
||||
VideoID string `json:"video_id"`
|
||||
Title string `json:"title"`
|
||||
VideoID string `json:"video_id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// CustomNavLink represents a simple custom navigation link stored in settings.custom_nav
|
||||
type CustomNavLink struct {
|
||||
Label string `json:"label"`
|
||||
URL string `json:"url"`
|
||||
External bool `json:"external"`
|
||||
Label string `json:"label"`
|
||||
URL string `json:"url"`
|
||||
External bool `json:"external"`
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
@@ -257,7 +257,7 @@ type Settings struct {
|
||||
// FrontendBaseURL: e.g. https://club.example.com
|
||||
FrontendBaseURL string `json:"frontend_base_url"`
|
||||
// APIBaseURL: full API root, e.g. https://api.example.com/api/v1 or https://backend.example.com/api/v1
|
||||
APIBaseURL string `json:"api_base_url"`
|
||||
APIBaseURL string `json:"api_base_url"`
|
||||
|
||||
// Social profiles
|
||||
FacebookURL string `json:"facebook_url"`
|
||||
@@ -279,10 +279,10 @@ type Settings struct {
|
||||
VideosItemsJSON string `gorm:"type:text" json:"-"`
|
||||
|
||||
// Title overrides for auto-fetched videos (stored as JSON array of {video_id,title})
|
||||
VideosOverridesJSON string `gorm:"type:text" json:"-"`
|
||||
VideosOverrides []VideoTitleOverride `gorm:"-" json:"videos_overrides,omitempty"`
|
||||
VideosOverridesJSON string `gorm:"type:text" json:"-"`
|
||||
VideosOverrides []VideoTitleOverride `gorm:"-" json:"videos_overrides,omitempty"`
|
||||
// Derived helper for API responses (map form used by frontend/admin): video_id -> title
|
||||
VideosTitleOverrides map[string]string `gorm:"-" json:"videos_title_overrides,omitempty"`
|
||||
VideosTitleOverrides map[string]string `gorm:"-" json:"videos_title_overrides,omitempty"`
|
||||
|
||||
// Merch module configuration
|
||||
MerchModuleEnabled bool `json:"merch_module_enabled"`
|
||||
@@ -313,25 +313,25 @@ type Settings struct {
|
||||
NewsletterQuietEnd int `json:"newsletter_quiet_end"` // 0-23
|
||||
|
||||
// Contact/Location information for map
|
||||
ContactAddress string `json:"contact_address"`
|
||||
ContactCity string `json:"contact_city"`
|
||||
ContactZip string `json:"contact_zip"`
|
||||
ContactCountry string `json:"contact_country"`
|
||||
ContactPhone string `json:"contact_phone"`
|
||||
ContactEmail string `json:"contact_email"`
|
||||
ContactAddress string `json:"contact_address"`
|
||||
ContactCity string `json:"contact_city"`
|
||||
ContactZip string `json:"contact_zip"`
|
||||
ContactCountry string `json:"contact_country"`
|
||||
ContactPhone string `json:"contact_phone"`
|
||||
ContactEmail string `json:"contact_email"`
|
||||
// Contact form auto-forwarding
|
||||
ContactForwardEnabled bool `json:"contact_forward_enabled"`
|
||||
ContactForwardList string `gorm:"type:text" json:"contact_forward_list"` // comma/space/semicolon-separated emails
|
||||
LocationLatitude float64 `json:"location_latitude"`
|
||||
LocationLongitude float64 `json:"location_longitude"`
|
||||
MapZoomLevel int `gorm:"default:15" json:"map_zoom_level"`
|
||||
MapStyle string `json:"map_style"`
|
||||
ShowMapOnHomepage bool `json:"show_map_on_homepage"`
|
||||
ContactForwardEnabled bool `json:"contact_forward_enabled"`
|
||||
ContactForwardList string `gorm:"type:text" json:"contact_forward_list"` // comma/space/semicolon-separated emails
|
||||
LocationLatitude float64 `json:"location_latitude"`
|
||||
LocationLongitude float64 `json:"location_longitude"`
|
||||
MapZoomLevel int `gorm:"default:15" json:"map_zoom_level"`
|
||||
MapStyle string `json:"map_style"`
|
||||
ShowMapOnHomepage bool `json:"show_map_on_homepage"`
|
||||
|
||||
// Homepage matches display configuration
|
||||
FinishedMatchDisplayDays int `gorm:"default:2" json:"finished_match_display_days"`
|
||||
StorageQuotaMB int `json:"storage_quota_mb"`
|
||||
StorageWarnThreshold int `json:"storage_warn_threshold"`
|
||||
StorageQuotaMB int `json:"storage_quota_mb"`
|
||||
StorageWarnThreshold int `json:"storage_warn_threshold"`
|
||||
StorageCriticalThreshold int `json:"storage_critical_threshold"`
|
||||
|
||||
// External error-review integration
|
||||
@@ -345,7 +345,6 @@ type Settings struct {
|
||||
// TableName specifies table name for Settings model
|
||||
func (Settings) TableName() string { return "settings" }
|
||||
|
||||
|
||||
// LoadCustomNav hydrates the in-memory CustomNav slice from the persisted JSON string.
|
||||
func (s *Settings) LoadCustomNav() {
|
||||
if s.CustomNavJSON == "" {
|
||||
@@ -416,14 +415,14 @@ func (Club) TableName() string {
|
||||
|
||||
// ContactCategory represents a category for organizing contacts (e.g., "Management", "Coaches", "Office")
|
||||
type ContactCategory struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
Name string `gorm:"not null;uniqueIndex" json:"name"`
|
||||
Description string `json:"description"`
|
||||
DisplayOrder int `gorm:"default:0" json:"display_order"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
Name string `gorm:"not null;uniqueIndex" json:"name"`
|
||||
Description string `json:"description"`
|
||||
DisplayOrder int `gorm:"default:0" json:"display_order"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
}
|
||||
|
||||
// TableName specifies the table name for the ContactCategory model
|
||||
@@ -433,20 +432,20 @@ func (ContactCategory) TableName() string {
|
||||
|
||||
// Contact represents a contact person (e.g., coach, manager, office staff)
|
||||
type Contact struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
CategoryID *uint `gorm:"index" json:"category_id,omitempty"`
|
||||
Category *ContactCategory `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Position string `json:"position"` // e.g., "Head Coach", "President"
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
ImageURL string `json:"image_url"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
DisplayOrder int `gorm:"default:0" json:"display_order"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
CategoryID *uint `gorm:"index" json:"category_id,omitempty"`
|
||||
Category *ContactCategory `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Position string `json:"position"` // e.g., "Head Coach", "President"
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
ImageURL string `json:"image_url"`
|
||||
Description string `gorm:"type:text" json:"description"`
|
||||
DisplayOrder int `gorm:"default:0" json:"display_order"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
}
|
||||
|
||||
// TableName specifies the table name for the Contact model
|
||||
|
||||
@@ -17,21 +17,24 @@ const (
|
||||
// NavigationItem represents a single navigation menu item
|
||||
type NavigationItem struct {
|
||||
gorm.Model
|
||||
Label string `gorm:"not null" json:"label"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Type NavigationItemType `gorm:"not null;default:'internal'" json:"type"`
|
||||
PageType string `json:"page_type,omitempty"` // e.g., 'blog', 'about', 'calendar'
|
||||
PageID *uint `json:"page_id,omitempty"` // optional reference to specific content
|
||||
Visible bool `gorm:"not null;default:true" json:"visible"`
|
||||
DisplayOrder int `gorm:"not null;default:0" json:"display_order"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Parent *NavigationItem `gorm:"foreignKey:ParentID" json:"parent,omitempty"`
|
||||
Children []NavigationItem `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||
Target string `gorm:"default:'_self'" json:"target"` // _self or _blank
|
||||
CSSClass string `json:"css_class,omitempty"`
|
||||
RequiresAuth bool `gorm:"default:false" json:"requires_auth"`
|
||||
RequiresAdmin bool `gorm:"default:false" json:"requires_admin"`
|
||||
Label string `gorm:"not null" json:"label"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Type NavigationItemType `gorm:"not null;default:'internal'" json:"type"`
|
||||
PageType string `json:"page_type,omitempty"` // e.g., 'blog', 'about', 'calendar'
|
||||
PageID *uint `json:"page_id,omitempty"` // optional reference to specific content
|
||||
Visible bool `gorm:"not null;default:true" json:"visible"`
|
||||
DisplayOrder int `gorm:"not null;default:0" json:"display_order"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Parent *NavigationItem `gorm:"foreignKey:ParentID" json:"parent,omitempty"`
|
||||
Children []NavigationItem `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||
Target string `gorm:"default:'_self'" json:"target"` // _self or _blank
|
||||
CSSClass string `json:"css_class,omitempty"`
|
||||
RequiresAuth bool `gorm:"default:false" json:"requires_auth"`
|
||||
RequiresAdmin bool `gorm:"default:false" json:"requires_admin"`
|
||||
// AllowEditor indicates that editors are allowed to access the corresponding admin page
|
||||
// when this item represents an admin navigation entry (RequiresAdmin=true).
|
||||
AllowEditor bool `gorm:"default:false" json:"allow_editor"`
|
||||
}
|
||||
|
||||
// TableName specifies the table name for the NavigationItem model
|
||||
@@ -44,7 +47,7 @@ func (n *NavigationItem) GetURL() string {
|
||||
if n.URL != "" {
|
||||
return n.URL
|
||||
}
|
||||
|
||||
|
||||
// Map page types to URLs for frontend
|
||||
if n.Type == NavTypePage && n.PageType != "" {
|
||||
pageURLMap := map[string]string{
|
||||
@@ -66,47 +69,47 @@ func (n *NavigationItem) GetURL() string {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Map admin page types to URLs
|
||||
if (n.Type == NavTypeInternal || n.Type == NavTypePage) && n.PageType != "" && n.RequiresAdmin {
|
||||
adminURLMap := map[string]string{
|
||||
"dashboard": "/admin",
|
||||
"analytics": "/admin/analytika",
|
||||
"teams": "/admin/tymy",
|
||||
"matches": "/admin/zapasy",
|
||||
"activities": "/admin/aktivity",
|
||||
"players": "/admin/hraci",
|
||||
"articles": "/admin/clanky",
|
||||
"categories": "/admin/kategorie",
|
||||
"about": "/admin/o-klubu",
|
||||
"videos": "/admin/videa",
|
||||
"gallery": "/admin/galerie",
|
||||
"scoreboard": "/admin/scoreboard",
|
||||
"scoreboard_remote": "/admin/scoreboard/remote",
|
||||
"clothing": "/admin/obleceni",
|
||||
"sponsors": "/admin/sponzori",
|
||||
"banners": "/admin/bannery",
|
||||
"messages": "/admin/zpravy",
|
||||
"contacts": "/admin/kontakty",
|
||||
"newsletter": "/admin/newsletter",
|
||||
"polls": "/admin/ankety",
|
||||
"comments": "/admin/komentare",
|
||||
"sweepstakes": "/admin/sweepstakes",
|
||||
"navigation": "/admin/navigace",
|
||||
"dashboard": "/admin",
|
||||
"analytics": "/admin/analytika",
|
||||
"teams": "/admin/tymy",
|
||||
"matches": "/admin/zapasy",
|
||||
"activities": "/admin/aktivity",
|
||||
"players": "/admin/hraci",
|
||||
"articles": "/admin/clanky",
|
||||
"categories": "/admin/kategorie",
|
||||
"about": "/admin/o-klubu",
|
||||
"videos": "/admin/videa",
|
||||
"gallery": "/admin/galerie",
|
||||
"scoreboard": "/admin/scoreboard",
|
||||
"scoreboard_remote": "/admin/scoreboard/remote",
|
||||
"clothing": "/admin/obleceni",
|
||||
"sponsors": "/admin/sponzori",
|
||||
"banners": "/admin/bannery",
|
||||
"messages": "/admin/zpravy",
|
||||
"contacts": "/admin/kontakty",
|
||||
"newsletter": "/admin/newsletter",
|
||||
"polls": "/admin/ankety",
|
||||
"comments": "/admin/komentare",
|
||||
"sweepstakes": "/admin/sweepstakes",
|
||||
"navigation": "/admin/navigace",
|
||||
"competition_aliases": "/admin/aliasy-soutezi",
|
||||
"prefetch": "/admin/prefetch",
|
||||
"users": "/admin/uzivatele",
|
||||
"settings": "/admin/nastaveni",
|
||||
"shortlinks": "/admin/shortlinks",
|
||||
"files": "/admin/soubory",
|
||||
"docs": "/admin/docs",
|
||||
"engagement": "/admin/engagement",
|
||||
"prefetch": "/admin/prefetch",
|
||||
"users": "/admin/uzivatele",
|
||||
"settings": "/admin/nastaveni",
|
||||
"shortlinks": "/admin/shortlinks",
|
||||
"files": "/admin/soubory",
|
||||
"docs": "/admin/docs",
|
||||
"engagement": "/admin/engagement",
|
||||
}
|
||||
if url, ok := adminURLMap[n.PageType]; ok {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return "#"
|
||||
}
|
||||
|
||||
@@ -130,7 +133,7 @@ func (s *SocialLink) GetIconName() string {
|
||||
if s.Icon != "" {
|
||||
return s.Icon
|
||||
}
|
||||
|
||||
|
||||
iconMap := map[string]string{
|
||||
"facebook": "FaFacebook",
|
||||
"instagram": "FaInstagram",
|
||||
@@ -141,10 +144,10 @@ func (s *SocialLink) GetIconName() string {
|
||||
"discord": "FaDiscord",
|
||||
"twitch": "FaTwitch",
|
||||
}
|
||||
|
||||
|
||||
if icon, ok := iconMap[s.Platform]; ok {
|
||||
return icon
|
||||
}
|
||||
|
||||
|
||||
return "FaLink"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user