mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #89
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
type ErrorEvent struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Origin string `json:"origin"`
|
||||
Language string `json:"language"`
|
||||
Severity string `json:"severity"`
|
||||
Message string `json:"message"`
|
||||
Stack string `json:"stack"`
|
||||
Component string `json:"component"`
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
URL string `json:"url"`
|
||||
Method string `json:"method"`
|
||||
Status int `json:"status"`
|
||||
RequestID string `json:"request_id"`
|
||||
UserID *uint `json:"user_id"`
|
||||
SessionToken string `json:"session_token"`
|
||||
Tags datatypes.JSON `json:"tags" gorm:"type:jsonb"`
|
||||
Context datatypes.JSON `json:"context" gorm:"type:jsonb"`
|
||||
Env string `json:"env"`
|
||||
Version string `json:"version"`
|
||||
Hostname string `json:"hostname"`
|
||||
OccurredAt time.Time `json:"occurred_at"`
|
||||
}
|
||||
+130
-63
@@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -19,38 +20,84 @@ type User struct {
|
||||
LastLogin *time.Time `json:"last_login,omitempty"`
|
||||
}
|
||||
|
||||
// LoadVideosOverrides hydrates the in-memory VideosOverrides slice from the persisted JSON column.
|
||||
func (s *Settings) LoadVideosOverrides() {
|
||||
if s.VideosOverridesJSON == "" {
|
||||
s.VideosOverrides = nil
|
||||
return
|
||||
}
|
||||
var items []VideoTitleOverride
|
||||
if err := json.Unmarshal([]byte(s.VideosOverridesJSON), &items); err != nil {
|
||||
s.VideosOverrides = nil
|
||||
return
|
||||
}
|
||||
s.VideosOverrides = items
|
||||
}
|
||||
|
||||
// SetVideosOverrides stores provided overrides and updates serialized JSON column.
|
||||
func (s *Settings) SetVideosOverrides(items []VideoTitleOverride) error {
|
||||
s.VideosOverrides = items
|
||||
if len(items) == 0 {
|
||||
s.VideosOverridesJSON = ""
|
||||
return nil
|
||||
}
|
||||
b, err := json.Marshal(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.VideosOverridesJSON = string(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Settings) LoadVideosTitleOverrides() {
|
||||
if s.VideosOverrides == nil && s.VideosOverridesJSON != "" {
|
||||
s.LoadVideosOverrides()
|
||||
}
|
||||
m := map[string]string{}
|
||||
if len(s.VideosOverrides) > 0 {
|
||||
for _, it := range s.VideosOverrides {
|
||||
vid := strings.TrimSpace(it.VideoID)
|
||||
t := strings.TrimSpace(it.Title)
|
||||
if vid != "" && t != "" {
|
||||
m[vid] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
s.VideosTitleOverrides = m
|
||||
}
|
||||
|
||||
// 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
|
||||
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"`
|
||||
@@ -80,43 +127,43 @@ 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"`
|
||||
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"`
|
||||
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"`
|
||||
Gender string `json:"gender"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
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"`
|
||||
Gender string `json:"gender"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
// Sponsor represents a sponsor
|
||||
@@ -135,6 +182,19 @@ type Sponsor struct {
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
// VideoTitleOverride represents a per-video title override (for auto YouTube source)
|
||||
type VideoTitleOverride struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
gorm.Model
|
||||
// Frontpage layout and style variants (e.g., "classic", "grid"; "light", "dark")
|
||||
@@ -218,6 +278,12 @@ type Settings struct {
|
||||
VideosJSON string `gorm:"type:text" json:"-"`
|
||||
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"`
|
||||
// Derived helper for API responses (map form used by frontend/admin): video_id -> title
|
||||
VideosTitleOverrides map[string]string `gorm:"-" json:"videos_title_overrides,omitempty"`
|
||||
|
||||
// Merch module configuration
|
||||
MerchModuleEnabled bool `json:"merch_module_enabled"`
|
||||
MerchStyle string `json:"merch_style"` // grid | slider (future)
|
||||
@@ -267,17 +333,18 @@ type Settings struct {
|
||||
StorageQuotaMB int `json:"storage_quota_mb"`
|
||||
StorageWarnThreshold int `json:"storage_warn_threshold"`
|
||||
StorageCriticalThreshold int `json:"storage_critical_threshold"`
|
||||
|
||||
// External error-review integration
|
||||
ErrorReviewIngestURL string `json:"error_review_ingest_url"`
|
||||
ErrorReviewIngestToken string `json:"error_review_ingest_token"`
|
||||
ErrorReviewAdminURL string `json:"error_review_admin_url"`
|
||||
ErrorReviewAdminToken string `json:"error_review_admin_token"`
|
||||
ErrorReviewUIURL string `json:"error_review_ui_url"`
|
||||
}
|
||||
|
||||
// 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() {
|
||||
|
||||
@@ -84,7 +84,6 @@ func (n *NavigationItem) GetURL() string {
|
||||
"scoreboard": "/admin/scoreboard",
|
||||
"scoreboard_remote": "/admin/scoreboard/remote",
|
||||
"clothing": "/admin/obleceni",
|
||||
"media": "/admin/media",
|
||||
"sponsors": "/admin/sponzori",
|
||||
"banners": "/admin/bannery",
|
||||
"messages": "/admin/zpravy",
|
||||
|
||||
@@ -16,6 +16,8 @@ type Sweepstake struct {
|
||||
PickerStyle string `json:"picker_style" gorm:"size:16;not null;default:'wheel'"` // wheel|cycler
|
||||
TotalPrizes int `json:"total_prizes" gorm:"not null;default:1"`
|
||||
PrizeSummary string `json:"prize_summary" gorm:"type:text"`
|
||||
EntryCostPoints int `json:"entry_cost_points" gorm:"not null;default:0"`
|
||||
EntryFeeCZK float64 `json:"entry_fee_czk" gorm:"not null;default:0"`
|
||||
WinnersSelectedAt *time.Time `json:"winners_selected_at"`
|
||||
VisibilityUntil *time.Time `json:"visibility_until"`
|
||||
DrawSeed string `json:"draw_seed" gorm:"size:64"`
|
||||
|
||||
Reference in New Issue
Block a user