mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
371 lines
15 KiB
Go
371 lines
15 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// User represents a user in the system
|
|
type User struct {
|
|
gorm.Model
|
|
Email string `gorm:"uniqueIndex;not null" json:"email"`
|
|
Password string `gorm:"not null" json:"-"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
Role string `gorm:"default:editor" json:"role"` // admin, editor
|
|
IsActive bool `gorm:"default:true"`
|
|
LastLogin *time.Time `json:"last_login,omitempty"`
|
|
}
|
|
|
|
// Article represents a blog article
|
|
type Article struct {
|
|
gorm.Model
|
|
Title string `gorm:"not null" json:"title"`
|
|
Content string `gorm:"type:text;not null" json:"content"`
|
|
AuthorID *uint `gorm:"index" json:"author_id,omitempty"`
|
|
Author *User `gorm:"foreignKey:AuthorID" json:"author,omitempty"`
|
|
CategoryID *uint `gorm:"index" json:"category_id,omitempty"`
|
|
Category *Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
|
|
ImageURL string `json:"image_url"`
|
|
Published bool `gorm:"default:false" json:"published"`
|
|
PublishedAt *time.Time `json:"published_at,omitempty"`
|
|
Slug string `gorm:"uniqueIndex" json:"slug"`
|
|
Excerpt string `gorm:"type:text" json:"excerpt"`
|
|
Featured bool `gorm:"default:false;index" json:"featured"`
|
|
// Fields for SEO and social previews
|
|
SEOTitle string `json:"seo_title"`
|
|
SEODescription string `gorm:"type:text" json:"seo_description"`
|
|
// OG image for social sharing (optional)
|
|
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)
|
|
// 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", ...]
|
|
// Gallery association (optional)
|
|
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"`
|
|
// YouTube video association (optional)
|
|
YouTubeVideoID string `json:"youtube_video_id"`
|
|
YouTubeVideoTitle string `gorm:"type:text" json:"youtube_video_title"`
|
|
YouTubeVideoURL string `json:"youtube_video_url"`
|
|
YouTubeVideoThumbnail string `json:"youtube_video_thumbnail"`
|
|
// Match link (loaded separately, not stored in this table)
|
|
// Removed omitempty to always include in JSON (even if null)
|
|
MatchLink *ArticleMatchLink `gorm:"-" json:"match_link"`
|
|
}
|
|
|
|
// ArticleTeamLink represents a link from an article to a team identified by an external FACR ID
|
|
type ArticleTeamLink struct {
|
|
gorm.Model
|
|
ArticleID uint `gorm:"not null;index" json:"article_id"`
|
|
Article Article `gorm:"foreignKey:ArticleID" json:"-"`
|
|
ExternalTeamID string `gorm:"not null;index" json:"external_team_id"`
|
|
TeamName string `json:"team_name"`
|
|
}
|
|
|
|
func (ArticleTeamLink) TableName() string { return "article_team_links" }
|
|
|
|
// ArticleMatchLink represents a link from an article to a match identified by an external FACR match ID
|
|
type ArticleMatchLink struct {
|
|
gorm.Model
|
|
ArticleID uint `gorm:"not null;index" json:"article_id"`
|
|
Article Article `gorm:"foreignKey:ArticleID" json:"-"`
|
|
ExternalMatchID string `gorm:"not null;index" json:"external_match_id"`
|
|
Title string `json:"title"`
|
|
}
|
|
|
|
func (ArticleMatchLink) TableName() string { return "article_match_links" }
|
|
|
|
// Team represents a football team
|
|
type Team struct {
|
|
gorm.Model
|
|
Name string `gorm:"not null"`
|
|
ShortName string
|
|
Description string
|
|
LogoURL string `json:"logo_url"`
|
|
IsActive bool `gorm:"default:true"`
|
|
}
|
|
|
|
// Player represents a football player
|
|
type Player struct {
|
|
gorm.Model
|
|
FirstName string `gorm:"not null" json:"first_name"`
|
|
LastName string `gorm:"not null" json:"last_name"`
|
|
DateOfBirth time.Time `json:"date_of_birth"`
|
|
Position string `json:"position"`
|
|
JerseyNumber int `json:"jersey_number"`
|
|
TeamID uint `json:"team_id"`
|
|
Team Team `gorm:"foreignKey:TeamID" json:"team"`
|
|
Nationality string `json:"nationality"`
|
|
Height int `json:"height"` // in cm
|
|
Weight int `json:"weight"` // in kg
|
|
ImageURL string `json:"image_url"`
|
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
|
Email string `json:"email"`
|
|
Phone string `json:"phone"`
|
|
}
|
|
|
|
// Sponsor represents a sponsor
|
|
type Sponsor struct {
|
|
gorm.Model
|
|
Name string `gorm:"not null" json:"name"`
|
|
LogoURL string `json:"logo_url"`
|
|
WebsiteURL string `json:"website_url"`
|
|
Description string `json:"description"`
|
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
|
Tier string `gorm:"default:'standard'" json:"tier"` // general (hlavní), standard
|
|
DisplayOrder int `gorm:"default:0" json:"display_order"` // For custom ordering
|
|
// Banner-specific metadata (optional)
|
|
Placement string `json:"placement"` // e.g., homepage_top, homepage_sidebar
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
}
|
|
|
|
type Settings struct {
|
|
gorm.Model
|
|
// Frontpage layout and style variants (e.g., "classic", "grid"; "light", "dark")
|
|
FrontpageLayout string `json:"frontpage_layout"`
|
|
FrontpageStyle string `json:"frontpage_style"`
|
|
// Sponsors module display preferences
|
|
SponsorsLayout string `json:"sponsors_layout"` // grid | slider | scroller | pyramid
|
|
SponsorsTheme string `json:"sponsors_theme"` // light | dark
|
|
// FACR club selection
|
|
ClubID string `json:"club_id"` // UUID from fotbal.cz
|
|
ClubType string `json:"club_type"` // football|futsal
|
|
ClubName string `json:"club_name"`
|
|
ClubLogoURL string `json:"club_logo_url"`
|
|
ClubURL string `json:"club_url"`
|
|
|
|
// Theme customization: colors & fonts
|
|
PrimaryColor string `json:"primary_color"` // e.g. #0033aa
|
|
SecondaryColor string `json:"secondary_color"`
|
|
AccentColor string `json:"accent_color"`
|
|
BackgroundColor string `json:"background_color"`
|
|
TextColor string `json:"text_color"`
|
|
FontHeading string `json:"font_heading"` // e.g. "Poppins, sans-serif"
|
|
FontBody string `json:"font_body"`
|
|
|
|
// Custom assets: raw overrides stored as TEXT
|
|
CustomCSS string `gorm:"type:text" json:"custom_css"`
|
|
CustomJS string `gorm:"type:text" json:"custom_js"`
|
|
CustomHTMLHome string `gorm:"type:text" json:"custom_html_home"`
|
|
CustomHTMLBlogList string `gorm:"type:text" json:"custom_html_blog_list"`
|
|
CustomHTMLBlogPost string `gorm:"type:text" json:"custom_html_blog_post"`
|
|
|
|
// Custom pages & navigation
|
|
AboutHTML string `gorm:"type:text" json:"about_html"`
|
|
ShowAboutInNav bool `gorm:"default:true" json:"show_about_in_nav"`
|
|
CustomNavJSON string `gorm:"type:text" json:"-"`
|
|
CustomNav []CustomNavLink `gorm:"-" json:"custom_nav,omitempty"`
|
|
|
|
// SMTP configuration (optional, overrides environment when present)
|
|
SMTPHost string `json:"smtp_host"`
|
|
SMTPPort int `json:"smtp_port"`
|
|
SMTPUser string `json:"smtp_user"`
|
|
SMTPPassword string `json:"smtp_password"`
|
|
SMTPFrom string `json:"smtp_from"`
|
|
SMTPFromName string `json:"smtp_from_name"`
|
|
SMTPEncryption string `json:"smtp_encryption"` // tls|ssl|none
|
|
SMTPAuth bool `json:"smtp_auth"`
|
|
SMTPSkipVerify bool `json:"smtp_skip_verify"`
|
|
|
|
// SEO defaults (site-wide)
|
|
SiteTitle string `json:"site_title"`
|
|
SiteDescription string `json:"site_description"`
|
|
MetaKeywords string `json:"meta_keywords"` // comma-separated
|
|
DefaultOGImageURL string `json:"default_og_image_url"`
|
|
TwitterHandle string `json:"twitter_handle"` // e.g. @club
|
|
CanonicalBaseURL string `json:"canonical_base_url"` // e.g. https://www.club.cz
|
|
AdditionalMeta string `gorm:"type:text" json:"additional_meta"` // raw extra meta
|
|
EnableIndexing bool `json:"enable_indexing"` // robots allow/disallow
|
|
|
|
// Social profiles
|
|
FacebookURL string `json:"facebook_url"`
|
|
InstagramURL string `json:"instagram_url"`
|
|
YoutubeURL string `json:"youtube_url"`
|
|
|
|
// Generic gallery link (preferred over legacy specific providers)
|
|
GalleryURL string `json:"gallery_url"`
|
|
GalleryLabel string `json:"gallery_label"`
|
|
|
|
// Videos module configuration
|
|
VideosModuleEnabled bool `json:"videos_module_enabled"`
|
|
VideosStyle string `json:"videos_style"` // slider | grid3 | grid
|
|
VideosSource string `json:"videos_source"` // auto | manual
|
|
VideosLimit int `json:"videos_limit"` // number of items on homepage
|
|
|
|
// Manual videos storage (JSON strings)
|
|
VideosJSON string `gorm:"type:text" json:"-"`
|
|
VideosItemsJSON string `gorm:"type:text" json:"-"`
|
|
|
|
// Merch module configuration
|
|
MerchModuleEnabled bool `json:"merch_module_enabled"`
|
|
MerchStyle string `json:"merch_style"` // grid | slider (future)
|
|
MerchSource string `json:"merch_source"` // manual | auto (future)
|
|
MerchLimit int `json:"merch_limit"`
|
|
// Manual merch storage
|
|
MerchItemsJSON string `gorm:"type:text" json:"-"`
|
|
|
|
// Newsletter automation toggle (persisted)
|
|
NewsletterEnabled bool `json:"newsletter_enabled"`
|
|
// Newsletter defaults
|
|
DefaultDigestType string `json:"default_digest_type"` // blogs|events|matches|scores|weekly
|
|
DefaultDigestCompetitions string `json:"default_digest_competitions"` // comma-separated codes
|
|
|
|
// Newsletter scheduling (admin-configurable)
|
|
// Enable/disable specific automated digests
|
|
EnableWeekly bool `json:"enable_weekly"`
|
|
EnableMatchReminders bool `json:"enable_match_reminders"`
|
|
EnableResults bool `json:"enable_results"`
|
|
// Weekly schedule
|
|
NewsletterWeeklyDay string `json:"newsletter_weekly_day"` // sun|mon|tue|wed|thu|fri|sat
|
|
NewsletterWeeklyHour int `json:"newsletter_weekly_hour"` // 0-23 local time
|
|
// Match reminder lead time (hours before kickoff)
|
|
NewsletterReminderLeadHours int `json:"newsletter_reminder_lead_hours"` // default 48
|
|
// Quiet hours for results (local time, inclusive start, exclusive end)
|
|
NewsletterQuietStart int `json:"newsletter_quiet_start"` // 0-23
|
|
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"`
|
|
LocationLatitude float64 `json:"location_latitude"`
|
|
LocationLongitude float64 `json:"location_longitude"`
|
|
MapZoomLevel int `gorm:"default:15" json:"map_zoom_level"`
|
|
MapStyle string `json:"map_style"` // OpenStreetMap style URL or preset: default, dark, satellite
|
|
ShowMapOnHomepage bool `json:"show_map_on_homepage"`
|
|
|
|
// Homepage matches display configuration
|
|
FinishedMatchDisplayDays int `gorm:"default:2" json:"finished_match_display_days"` // Number of days to show finished matches with scores on homepage
|
|
}
|
|
|
|
// TableName specifies table name for Settings model
|
|
func (Settings) TableName() string { return "settings" }
|
|
|
|
// CustomNavLink represents an item in the main navigation managed via settings
|
|
type CustomNavLink struct {
|
|
Label string `json:"label"`
|
|
URL string `json:"url"`
|
|
External bool `json:"external"`
|
|
}
|
|
|
|
// LoadCustomNav hydrates the in-memory CustomNav slice from the persisted JSON string.
|
|
func (s *Settings) LoadCustomNav() {
|
|
if s.CustomNavJSON == "" {
|
|
s.CustomNav = nil
|
|
return
|
|
}
|
|
var items []CustomNavLink
|
|
if err := json.Unmarshal([]byte(s.CustomNavJSON), &items); err != nil {
|
|
s.CustomNav = nil
|
|
return
|
|
}
|
|
s.CustomNav = items
|
|
}
|
|
|
|
// SetCustomNav stores the provided navigation links and updates the serialized JSON column.
|
|
func (s *Settings) SetCustomNav(items []CustomNavLink) error {
|
|
s.CustomNav = items
|
|
if len(items) == 0 {
|
|
s.CustomNavJSON = ""
|
|
return nil
|
|
}
|
|
b, err := json.Marshal(items)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.CustomNavJSON = string(b)
|
|
return nil
|
|
}
|
|
|
|
// Club represents the main club/team configuration
|
|
type Club struct {
|
|
gorm.Model
|
|
Name string `gorm:"not null" json:"name"`
|
|
PrimaryColor string `gorm:"default:'#1a365d'" json:"primary_color"`
|
|
SportType string `gorm:"not null" json:"sport_type"`
|
|
LogoPath string `json:"logo_path"`
|
|
}
|
|
|
|
// TableName specifies the table name for the User model
|
|
func (User) TableName() string {
|
|
return "users"
|
|
}
|
|
|
|
// TableName specifies the table name for the Article model
|
|
func (Article) TableName() string {
|
|
return "articles"
|
|
}
|
|
|
|
// TableName specifies the table name for the Team model
|
|
func (Team) TableName() string {
|
|
return "teams"
|
|
}
|
|
|
|
// TableName specifies the table name for the Player model
|
|
func (Player) TableName() string {
|
|
return "players"
|
|
}
|
|
|
|
// TableName specifies the table name for the Sponsor model
|
|
func (Sponsor) TableName() string {
|
|
return "sponsors"
|
|
}
|
|
|
|
// TableName specifies the table name for the Club model
|
|
func (Club) TableName() string {
|
|
return "clubs"
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// TableName specifies the table name for the ContactCategory model
|
|
func (ContactCategory) TableName() string {
|
|
return "contact_categories"
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// TableName specifies the table name for the Contact model
|
|
func (Contact) TableName() string {
|
|
return "contacts"
|
|
}
|