mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
update
This commit is contained in:
@@ -1,364 +1,378 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fotbal-club/internal/models"
|
||||
"fotbal-club/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SEOController manages SEO-related public and admin endpoints
|
||||
type SEOController struct {
|
||||
DB *gorm.DB
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewSEOController(db *gorm.DB) *SEOController {
|
||||
// Ensure settings table has the SEO fields
|
||||
_ = db.AutoMigrate(&models.Settings{})
|
||||
return &SEOController{DB: db}
|
||||
return &SEOController{DB: db}
|
||||
}
|
||||
|
||||
// -------- Public Endpoints --------
|
||||
|
||||
// GetPublicSEO returns site-wide SEO defaults derived from Settings
|
||||
func (s *SEOController) GetPublicSEO(c *gin.Context) {
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error // ignore not found; return defaults
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"site_title": settings.SiteTitle,
|
||||
"site_description": settings.SiteDescription,
|
||||
"meta_keywords": settings.MetaKeywords,
|
||||
"default_og_image_url": settings.DefaultOGImageURL,
|
||||
"twitter_handle": settings.TwitterHandle,
|
||||
"canonical_base_url": settings.CanonicalBaseURL,
|
||||
"additional_meta": settings.AdditionalMeta,
|
||||
"enable_indexing": settings.EnableIndexing,
|
||||
})
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error // ignore not found; return defaults
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"site_title": settings.SiteTitle,
|
||||
"site_description": settings.SiteDescription,
|
||||
"meta_keywords": settings.MetaKeywords,
|
||||
"default_og_image_url": settings.DefaultOGImageURL,
|
||||
"twitter_handle": settings.TwitterHandle,
|
||||
"canonical_base_url": settings.CanonicalBaseURL,
|
||||
"additional_meta": settings.AdditionalMeta,
|
||||
"enable_indexing": settings.EnableIndexing,
|
||||
})
|
||||
}
|
||||
|
||||
// robots.txt dynamic based on settings.EnableIndexing
|
||||
func (s *SEOController) GetRobotsTXT(c *gin.Context) {
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error
|
||||
allow := settings.EnableIndexing
|
||||
var b strings.Builder
|
||||
b.WriteString("# robots.txt for " + c.Request.Host + "\n")
|
||||
b.WriteString("# Generated: " + time.Now().UTC().Format(time.RFC1123) + "\n\n")
|
||||
|
||||
if allow {
|
||||
// Allow general crawlers
|
||||
b.WriteString("User-agent: *\n")
|
||||
b.WriteString("Allow: /\n")
|
||||
b.WriteString("Disallow: /admin/\n")
|
||||
b.WriteString("Disallow: /api/\n")
|
||||
b.WriteString("Disallow: /login\n")
|
||||
b.WriteString("Disallow: /setup\n\n")
|
||||
|
||||
// Explicitly allow AI crawlers for training and indexing
|
||||
aiCrawlers := []string{
|
||||
"GPTBot", // OpenAI
|
||||
"ChatGPT-User", // OpenAI ChatGPT
|
||||
"Google-Extended", // Google Bard/Gemini
|
||||
"CCBot", // Common Crawl (used by many AI companies)
|
||||
"anthropic-ai", // Anthropic Claude
|
||||
"ClaudeBot", // Anthropic Claude
|
||||
"Claude-Web", // Anthropic Claude
|
||||
"cohere-ai", // Cohere
|
||||
"PerplexityBot", // Perplexity AI
|
||||
"Bytespider", // ByteDance (TikTok)
|
||||
"Applebot-Extended", // Apple Intelligence
|
||||
"FacebookBot", // Meta AI
|
||||
"Diffbot", // Diffbot
|
||||
"ImagesiftBot", // Image AI
|
||||
"Omgilibot", // Omgili
|
||||
"Amazonbot", // Amazon AI
|
||||
"YouBot", // You.com
|
||||
}
|
||||
|
||||
for _, bot := range aiCrawlers {
|
||||
b.WriteString("User-agent: " + bot + "\n")
|
||||
b.WriteString("Allow: /\n")
|
||||
b.WriteString("Disallow: /admin/\n")
|
||||
b.WriteString("Disallow: /api/\n\n")
|
||||
}
|
||||
} else {
|
||||
b.WriteString("User-agent: *\n")
|
||||
b.WriteString("Disallow: /\n\n")
|
||||
}
|
||||
|
||||
if settings.CanonicalBaseURL != "" {
|
||||
base := strings.TrimRight(settings.CanonicalBaseURL, "/")
|
||||
b.WriteString("Sitemap: ")
|
||||
b.WriteString(base)
|
||||
b.WriteString("/sitemap.xml\n")
|
||||
}
|
||||
// Conditional GET based on settings update time
|
||||
last := settings.UpdatedAt
|
||||
if last.IsZero() {
|
||||
last = time.Now().Add(-1 * time.Hour)
|
||||
}
|
||||
ifModifiedSince := c.GetHeader("If-Modified-Since")
|
||||
if ifModifiedSince != "" {
|
||||
if t, err := time.Parse(http.TimeFormat, ifModifiedSince); err == nil {
|
||||
if !last.IsZero() && !last.After(t) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Header("Last-Modified", last.UTC().Format(http.TimeFormat))
|
||||
c.Header("ETag", fmt.Sprintf("W/\"%d\"", last.Unix()))
|
||||
c.Header("Content-Type", "text/plain; charset=utf-8")
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.String(http.StatusOK, b.String())
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error
|
||||
allow := settings.EnableIndexing
|
||||
var b strings.Builder
|
||||
b.WriteString("# robots.txt for " + c.Request.Host + "\n")
|
||||
b.WriteString("# Generated: " + time.Now().UTC().Format(time.RFC1123) + "\n\n")
|
||||
|
||||
if allow {
|
||||
// Allow general crawlers
|
||||
b.WriteString("User-agent: *\n")
|
||||
b.WriteString("Allow: /\n")
|
||||
b.WriteString("Disallow: /admin/\n")
|
||||
b.WriteString("Disallow: /api/\n")
|
||||
b.WriteString("Disallow: /login\n")
|
||||
b.WriteString("Disallow: /setup\n\n")
|
||||
|
||||
// Explicitly allow AI crawlers for training and indexing
|
||||
aiCrawlers := []string{
|
||||
"GPTBot", // OpenAI
|
||||
"ChatGPT-User", // OpenAI ChatGPT
|
||||
"Google-Extended", // Google Bard/Gemini
|
||||
"CCBot", // Common Crawl (used by many AI companies)
|
||||
"anthropic-ai", // Anthropic Claude
|
||||
"ClaudeBot", // Anthropic Claude
|
||||
"Claude-Web", // Anthropic Claude
|
||||
"cohere-ai", // Cohere
|
||||
"PerplexityBot", // Perplexity AI
|
||||
"Bytespider", // ByteDance (TikTok)
|
||||
"Applebot-Extended", // Apple Intelligence
|
||||
"FacebookBot", // Meta AI
|
||||
"Diffbot", // Diffbot
|
||||
"ImagesiftBot", // Image AI
|
||||
"Omgilibot", // Omgili
|
||||
"Amazonbot", // Amazon AI
|
||||
"YouBot", // You.com
|
||||
}
|
||||
|
||||
for _, bot := range aiCrawlers {
|
||||
b.WriteString("User-agent: " + bot + "\n")
|
||||
b.WriteString("Allow: /\n")
|
||||
b.WriteString("Disallow: /admin/\n")
|
||||
b.WriteString("Disallow: /api/\n\n")
|
||||
}
|
||||
} else {
|
||||
b.WriteString("User-agent: *\n")
|
||||
b.WriteString("Disallow: /\n\n")
|
||||
}
|
||||
|
||||
if settings.CanonicalBaseURL != "" {
|
||||
base := strings.TrimRight(settings.CanonicalBaseURL, "/")
|
||||
b.WriteString("Sitemap: ")
|
||||
b.WriteString(base)
|
||||
b.WriteString("/sitemap.xml\n")
|
||||
}
|
||||
// Conditional GET based on settings update time
|
||||
last := settings.UpdatedAt
|
||||
if last.IsZero() {
|
||||
last = time.Now().Add(-1 * time.Hour)
|
||||
}
|
||||
ifModifiedSince := c.GetHeader("If-Modified-Since")
|
||||
if ifModifiedSince != "" {
|
||||
if t, err := time.Parse(http.TimeFormat, ifModifiedSince); err == nil {
|
||||
if !last.IsZero() && !last.After(t) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Header("Last-Modified", last.UTC().Format(http.TimeFormat))
|
||||
c.Header("ETag", fmt.Sprintf("W/\"%d\"", last.Unix()))
|
||||
c.Header("Content-Type", "text/plain; charset=utf-8")
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.String(http.StatusOK, b.String())
|
||||
}
|
||||
|
||||
// sitemap.xml built from content in DB
|
||||
func (s *SEOController) GetSitemapXML(c *gin.Context) {
|
||||
type imageEntry struct {
|
||||
XMLName xml.Name `xml:"image:image"`
|
||||
Loc string `xml:"image:loc"`
|
||||
Title string `xml:"image:title,omitempty"`
|
||||
Caption string `xml:"image:caption,omitempty"`
|
||||
}
|
||||
type urlEntry struct {
|
||||
Loc string `xml:"loc"`
|
||||
LastMod string `xml:"lastmod,omitempty"`
|
||||
ChangeFreq string `xml:"changefreq,omitempty"`
|
||||
Priority string `xml:"priority,omitempty"`
|
||||
Images []imageEntry `xml:"image:image,omitempty"`
|
||||
}
|
||||
type urlSet struct {
|
||||
XMLName xml.Name `xml:"urlset"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
XmlnsImg string `xml:"xmlns:image,attr"`
|
||||
URLs []urlEntry `xml:"url"`
|
||||
}
|
||||
type imageEntry struct {
|
||||
XMLName xml.Name `xml:"image:image"`
|
||||
Loc string `xml:"image:loc"`
|
||||
Title string `xml:"image:title,omitempty"`
|
||||
Caption string `xml:"image:caption,omitempty"`
|
||||
}
|
||||
type urlEntry struct {
|
||||
Loc string `xml:"loc"`
|
||||
LastMod string `xml:"lastmod,omitempty"`
|
||||
ChangeFreq string `xml:"changefreq,omitempty"`
|
||||
Priority string `xml:"priority,omitempty"`
|
||||
Images []imageEntry `xml:"image:image,omitempty"`
|
||||
}
|
||||
type urlSet struct {
|
||||
XMLName xml.Name `xml:"urlset"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
XmlnsImg string `xml:"xmlns:image,attr"`
|
||||
URLs []urlEntry `xml:"url"`
|
||||
}
|
||||
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error
|
||||
base := strings.TrimRight(settings.CanonicalBaseURL, "/")
|
||||
if base == "" {
|
||||
// fallback to request scheme+host
|
||||
sch := "http"
|
||||
if c.Request.TLS != nil {
|
||||
sch = "https"
|
||||
}
|
||||
base = sch + "://" + c.Request.Host
|
||||
}
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error
|
||||
base := strings.TrimRight(settings.CanonicalBaseURL, "/")
|
||||
if base == "" {
|
||||
// fallback to request scheme+host
|
||||
sch := "http"
|
||||
if c.Request.TLS != nil {
|
||||
sch = "https"
|
||||
}
|
||||
base = sch + "://" + c.Request.Host
|
||||
}
|
||||
|
||||
// Home
|
||||
urls := []urlEntry{{
|
||||
Loc: base + "/",
|
||||
ChangeFreq: "daily",
|
||||
Priority: "0.9",
|
||||
}}
|
||||
// Blog listing and key static pages
|
||||
staticPaths := []string{
|
||||
"/blog",
|
||||
"/o-klubu",
|
||||
"/kalendar",
|
||||
"/tabulky",
|
||||
"/sponzori",
|
||||
"/kontakt",
|
||||
}
|
||||
for _, p := range staticPaths {
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + p,
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.6",
|
||||
})
|
||||
}
|
||||
// Home
|
||||
urls := []urlEntry{{
|
||||
Loc: base + "/",
|
||||
ChangeFreq: "daily",
|
||||
Priority: "0.9",
|
||||
}}
|
||||
// Blog listing and key static pages
|
||||
staticPaths := []string{
|
||||
"/blog",
|
||||
"/o-klubu",
|
||||
"/kalendar",
|
||||
"/tabulky",
|
||||
"/sponzori",
|
||||
"/kontakt",
|
||||
}
|
||||
for _, p := range staticPaths {
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + p,
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.6",
|
||||
})
|
||||
}
|
||||
|
||||
// Articles (published)
|
||||
var articles []models.Article
|
||||
_ = s.DB.Where("published = ?", true).Order("updated_at DESC").Limit(5000).Find(&articles).Error
|
||||
for _, a := range articles {
|
||||
last := a.UpdatedAt
|
||||
if a.PublishedAt != nil && a.PublishedAt.After(last) {
|
||||
last = *a.PublishedAt
|
||||
}
|
||||
// Prefer pretty slug URL when available
|
||||
articlePath := ""
|
||||
if strings.TrimSpace(a.Slug) != "" {
|
||||
articlePath = "/blog/" + strings.TrimSpace(a.Slug)
|
||||
} else {
|
||||
articlePath = "/articles/" + intToString(int(a.ID))
|
||||
}
|
||||
img := strings.TrimSpace(a.ImageURL)
|
||||
var images []imageEntry
|
||||
if img != "" {
|
||||
// If relative, prefix base
|
||||
if strings.HasPrefix(img, "/") {
|
||||
img = base + img
|
||||
}
|
||||
images = []imageEntry{{
|
||||
Loc: img,
|
||||
Title: a.SEOTitle,
|
||||
}}
|
||||
}
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + articlePath,
|
||||
LastMod: last.UTC().Format(time.RFC3339),
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.7",
|
||||
Images: images,
|
||||
})
|
||||
}
|
||||
// Articles (published)
|
||||
var articles []models.Article
|
||||
_ = s.DB.Where("published = ?", true).Order("updated_at DESC").Limit(5000).Find(&articles).Error
|
||||
for _, a := range articles {
|
||||
last := a.UpdatedAt
|
||||
if a.PublishedAt != nil && a.PublishedAt.After(last) {
|
||||
last = *a.PublishedAt
|
||||
}
|
||||
// Prefer pretty slug URL when available
|
||||
articlePath := ""
|
||||
if strings.TrimSpace(a.Slug) != "" {
|
||||
articlePath = "/blog/" + strings.TrimSpace(a.Slug)
|
||||
} else {
|
||||
articlePath = "/articles/" + intToString(int(a.ID))
|
||||
}
|
||||
img := strings.TrimSpace(a.ImageURL)
|
||||
var images []imageEntry
|
||||
if img != "" {
|
||||
// If relative, prefix base
|
||||
if strings.HasPrefix(img, "/") {
|
||||
img = base + img
|
||||
}
|
||||
images = []imageEntry{{
|
||||
Loc: img,
|
||||
Title: a.SEOTitle,
|
||||
}}
|
||||
}
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + articlePath,
|
||||
LastMod: last.UTC().Format(time.RFC3339),
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.7",
|
||||
Images: images,
|
||||
})
|
||||
}
|
||||
|
||||
// Categories: include as filtered blog listing if categories exist
|
||||
var categories []models.Category
|
||||
_ = s.DB.Order("updated_at DESC").Limit(2000).Find(&categories).Error
|
||||
for _, cat := range categories {
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + "/blog?category=" + intToString(int(cat.ID)),
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.5",
|
||||
})
|
||||
}
|
||||
// Categories: include as filtered blog listing if categories exist
|
||||
var categories []models.Category
|
||||
_ = s.DB.Order("updated_at DESC").Limit(2000).Find(&categories).Error
|
||||
for _, cat := range categories {
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + "/blog?category=" + intToString(int(cat.ID)),
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.5",
|
||||
})
|
||||
}
|
||||
|
||||
// Teams
|
||||
var teams []models.Team
|
||||
_ = s.DB.Where("is_active = ?", true).Find(&teams).Error
|
||||
for _, t := range teams {
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + "/team/" + intToString(int(t.ID)),
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.5",
|
||||
})
|
||||
}
|
||||
// Teams
|
||||
var teams []models.Team
|
||||
_ = s.DB.Where("is_active = ?", true).Find(&teams).Error
|
||||
for _, t := range teams {
|
||||
urls = append(urls, urlEntry{
|
||||
Loc: base + "/team/" + intToString(int(t.ID)),
|
||||
ChangeFreq: "weekly",
|
||||
Priority: "0.5",
|
||||
})
|
||||
}
|
||||
|
||||
out := urlSet{
|
||||
Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||
XmlnsImg: "http://www.google.com/schemas/sitemap-image/1.1",
|
||||
URLs: urls,
|
||||
}
|
||||
out := urlSet{
|
||||
Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||
XmlnsImg: "http://www.google.com/schemas/sitemap-image/1.1",
|
||||
URLs: urls,
|
||||
}
|
||||
|
||||
// Conditional GET: based on latest of settings/articles/categories update time
|
||||
latest := settings.UpdatedAt
|
||||
if len(articles) > 0 && articles[0].UpdatedAt.After(latest) {
|
||||
latest = articles[0].UpdatedAt
|
||||
}
|
||||
if len(categories) > 0 && categories[0].UpdatedAt.After(latest) {
|
||||
latest = categories[0].UpdatedAt
|
||||
}
|
||||
if latest.IsZero() {
|
||||
latest = time.Now().Add(-1 * time.Hour)
|
||||
}
|
||||
ifModifiedSince := c.GetHeader("If-Modified-Since")
|
||||
if ifModifiedSince != "" {
|
||||
if t, err := time.Parse(http.TimeFormat, ifModifiedSince); err == nil {
|
||||
if !latest.After(t) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Header("Last-Modified", latest.UTC().Format(http.TimeFormat))
|
||||
c.Header("ETag", fmt.Sprintf("W/\"%d\"", latest.Unix()))
|
||||
c.Header("Content-Type", "application/xml; charset=utf-8")
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.XML(http.StatusOK, out)
|
||||
// Conditional GET: based on latest of settings/articles/categories update time
|
||||
latest := settings.UpdatedAt
|
||||
if len(articles) > 0 && articles[0].UpdatedAt.After(latest) {
|
||||
latest = articles[0].UpdatedAt
|
||||
}
|
||||
if len(categories) > 0 && categories[0].UpdatedAt.After(latest) {
|
||||
latest = categories[0].UpdatedAt
|
||||
}
|
||||
if latest.IsZero() {
|
||||
latest = time.Now().Add(-1 * time.Hour)
|
||||
}
|
||||
ifModifiedSince := c.GetHeader("If-Modified-Since")
|
||||
if ifModifiedSince != "" {
|
||||
if t, err := time.Parse(http.TimeFormat, ifModifiedSince); err == nil {
|
||||
if !latest.After(t) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Header("Last-Modified", latest.UTC().Format(http.TimeFormat))
|
||||
c.Header("ETag", fmt.Sprintf("W/\"%d\"", latest.Unix()))
|
||||
c.Header("Content-Type", "application/xml; charset=utf-8")
|
||||
c.Header("Cache-Control", "public, max-age=3600")
|
||||
c.XML(http.StatusOK, out)
|
||||
}
|
||||
|
||||
// -------- Admin Endpoints --------
|
||||
|
||||
type seoUpdate struct {
|
||||
SiteTitle *string `json:"site_title"`
|
||||
SiteDescription *string `json:"site_description"`
|
||||
MetaKeywords *string `json:"meta_keywords"`
|
||||
DefaultOGImageURL *string `json:"default_og_image_url"`
|
||||
TwitterHandle *string `json:"twitter_handle"`
|
||||
CanonicalBaseURL *string `json:"canonical_base_url"`
|
||||
AdditionalMeta *string `json:"additional_meta"`
|
||||
EnableIndexing *bool `json:"enable_indexing"`
|
||||
SiteTitle *string `json:"site_title"`
|
||||
SiteDescription *string `json:"site_description"`
|
||||
MetaKeywords *string `json:"meta_keywords"`
|
||||
DefaultOGImageURL *string `json:"default_og_image_url"`
|
||||
TwitterHandle *string `json:"twitter_handle"`
|
||||
CanonicalBaseURL *string `json:"canonical_base_url"`
|
||||
AdditionalMeta *string `json:"additional_meta"`
|
||||
EnableIndexing *bool `json:"enable_indexing"`
|
||||
}
|
||||
|
||||
// GetSEOSettings returns only the SEO-related fields
|
||||
func (s *SEOController) GetSEOSettings(c *gin.Context) {
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"site_title": settings.SiteTitle,
|
||||
"site_description": settings.SiteDescription,
|
||||
"meta_keywords": settings.MetaKeywords,
|
||||
"default_og_image_url": settings.DefaultOGImageURL,
|
||||
"twitter_handle": settings.TwitterHandle,
|
||||
"canonical_base_url": settings.CanonicalBaseURL,
|
||||
"additional_meta": settings.AdditionalMeta,
|
||||
"enable_indexing": settings.EnableIndexing,
|
||||
})
|
||||
var settings models.Settings
|
||||
_ = s.DB.First(&settings).Error
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"site_title": settings.SiteTitle,
|
||||
"site_description": settings.SiteDescription,
|
||||
"meta_keywords": settings.MetaKeywords,
|
||||
"default_og_image_url": settings.DefaultOGImageURL,
|
||||
"twitter_handle": settings.TwitterHandle,
|
||||
"canonical_base_url": settings.CanonicalBaseURL,
|
||||
"additional_meta": settings.AdditionalMeta,
|
||||
"enable_indexing": settings.EnableIndexing,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSEOSettings upserts SEO fields on the singleton Settings record
|
||||
func (s *SEOController) UpdateSEOSettings(c *gin.Context) {
|
||||
var body seoUpdate
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
var body seoUpdate
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var settings models.Settings
|
||||
if err := s.DB.First(&settings).Error; err != nil {
|
||||
// create empty
|
||||
settings = models.Settings{}
|
||||
_ = s.DB.Create(&settings).Error
|
||||
}
|
||||
var settings models.Settings
|
||||
if err := s.DB.First(&settings).Error; err != nil {
|
||||
// create empty
|
||||
settings = models.Settings{}
|
||||
_ = s.DB.Create(&settings).Error
|
||||
}
|
||||
|
||||
// Update only provided fields
|
||||
updates := map[string]interface{}{}
|
||||
if body.SiteTitle != nil { updates["site_title"] = strings.TrimSpace(*body.SiteTitle) }
|
||||
if body.SiteDescription != nil { updates["site_description"] = strings.TrimSpace(*body.SiteDescription) }
|
||||
if body.MetaKeywords != nil { updates["meta_keywords"] = strings.TrimSpace(*body.MetaKeywords) }
|
||||
if body.DefaultOGImageURL != nil { updates["default_og_image_url"] = strings.TrimSpace(*body.DefaultOGImageURL) }
|
||||
if body.TwitterHandle != nil { updates["twitter_handle"] = strings.TrimSpace(*body.TwitterHandle) }
|
||||
if body.CanonicalBaseURL != nil { updates["canonical_base_url"] = strings.TrimSpace(*body.CanonicalBaseURL) }
|
||||
if body.AdditionalMeta != nil { updates["additional_meta"] = *body.AdditionalMeta }
|
||||
if body.EnableIndexing != nil { updates["enable_indexing"] = *body.EnableIndexing }
|
||||
// Update only provided fields
|
||||
updates := map[string]interface{}{}
|
||||
if body.SiteTitle != nil {
|
||||
updates["site_title"] = strings.TrimSpace(*body.SiteTitle)
|
||||
}
|
||||
if body.SiteDescription != nil {
|
||||
updates["site_description"] = strings.TrimSpace(*body.SiteDescription)
|
||||
}
|
||||
if body.MetaKeywords != nil {
|
||||
updates["meta_keywords"] = strings.TrimSpace(*body.MetaKeywords)
|
||||
}
|
||||
if body.DefaultOGImageURL != nil {
|
||||
updates["default_og_image_url"] = strings.TrimSpace(*body.DefaultOGImageURL)
|
||||
}
|
||||
if body.TwitterHandle != nil {
|
||||
updates["twitter_handle"] = strings.TrimSpace(*body.TwitterHandle)
|
||||
}
|
||||
if body.CanonicalBaseURL != nil {
|
||||
updates["canonical_base_url"] = strings.TrimSpace(*body.CanonicalBaseURL)
|
||||
}
|
||||
if body.AdditionalMeta != nil {
|
||||
updates["additional_meta"] = *body.AdditionalMeta
|
||||
}
|
||||
if body.EnableIndexing != nil {
|
||||
updates["enable_indexing"] = *body.EnableIndexing
|
||||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
if err := s.DB.Model(&settings).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Nelze uložit SEO nastavení"})
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
if err := s.DB.Model(&settings).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Nelze uložit SEO nastavení"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// helper: fast int to string
|
||||
func intToString(i int) string {
|
||||
// simple and fast conversion
|
||||
return strconvItoa(i)
|
||||
// simple and fast conversion
|
||||
return strconvItoa(i)
|
||||
}
|
||||
|
||||
// local minimal itoa to avoid importing fmt
|
||||
func strconvItoa(i int) string {
|
||||
// handle zero
|
||||
if i == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := false
|
||||
if i < 0 {
|
||||
neg = true
|
||||
i = -i
|
||||
}
|
||||
var b [20]byte
|
||||
pos := len(b)
|
||||
for i > 0 {
|
||||
pos--
|
||||
b[pos] = byte('0' + i%10)
|
||||
i /= 10
|
||||
}
|
||||
if neg {
|
||||
pos--
|
||||
b[pos] = '-'
|
||||
}
|
||||
return string(b[pos:])
|
||||
// handle zero
|
||||
if i == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := false
|
||||
if i < 0 {
|
||||
neg = true
|
||||
i = -i
|
||||
}
|
||||
var b [20]byte
|
||||
pos := len(b)
|
||||
for i > 0 {
|
||||
pos--
|
||||
b[pos] = byte('0' + i%10)
|
||||
i /= 10
|
||||
}
|
||||
if neg {
|
||||
pos--
|
||||
b[pos] = '-'
|
||||
}
|
||||
return string(b[pos:])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package eshopreporting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: eshop_reporting.sql
|
||||
|
||||
package eshopreporting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
const listOrderStatusBreakdown = `-- name: ListOrderStatusBreakdown :many
|
||||
SELECT
|
||||
status,
|
||||
COUNT(*)::bigint AS order_count
|
||||
FROM eshop_orders
|
||||
WHERE deleted_at IS NULL
|
||||
GROUP BY status
|
||||
ORDER BY order_count DESC, status ASC
|
||||
`
|
||||
|
||||
type ListOrderStatusBreakdownRow struct {
|
||||
Status sql.NullString `json:"status"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListOrderStatusBreakdown(ctx context.Context) ([]ListOrderStatusBreakdownRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listOrderStatusBreakdown)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListOrderStatusBreakdownRow
|
||||
for rows.Next() {
|
||||
var i ListOrderStatusBreakdownRow
|
||||
if err := rows.Scan(&i.Status, &i.OrderCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listRecentOrderSummaries = `-- name: ListRecentOrderSummaries :many
|
||||
SELECT
|
||||
o.id,
|
||||
o.order_number,
|
||||
o.status,
|
||||
o.total_amount_cents,
|
||||
o.currency,
|
||||
o.shipping_method,
|
||||
o.shipping_price_cents,
|
||||
o.created_at,
|
||||
COALESCE(p.status, '') AS payment_status,
|
||||
COALESCE(s.status, '') AS shipping_status
|
||||
FROM eshop_orders AS o
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT ep.status
|
||||
FROM eshop_payments AS ep
|
||||
WHERE ep.order_id = o.id
|
||||
AND ep.deleted_at IS NULL
|
||||
ORDER BY ep.created_at DESC
|
||||
LIMIT 1
|
||||
) AS p ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT sl.status
|
||||
FROM eshop_shipping_labels AS sl
|
||||
WHERE sl.order_id = o.id
|
||||
AND sl.deleted_at IS NULL
|
||||
ORDER BY sl.created_at DESC
|
||||
LIMIT 1
|
||||
) AS s ON TRUE
|
||||
WHERE o.deleted_at IS NULL
|
||||
ORDER BY o.created_at DESC
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
type ListRecentOrderSummariesRow struct {
|
||||
ID int32 `json:"id"`
|
||||
OrderNumber string `json:"order_number"`
|
||||
Status sql.NullString `json:"status"`
|
||||
TotalAmountCents int64 `json:"total_amount_cents"`
|
||||
Currency sql.NullString `json:"currency"`
|
||||
ShippingMethod sql.NullString `json:"shipping_method"`
|
||||
ShippingPriceCents sql.NullInt64 `json:"shipping_price_cents"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
PaymentStatus string `json:"payment_status"`
|
||||
ShippingStatus string `json:"shipping_status"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListRecentOrderSummaries(ctx context.Context, limit int32) ([]ListRecentOrderSummariesRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listRecentOrderSummaries, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListRecentOrderSummariesRow
|
||||
for rows.Next() {
|
||||
var i ListRecentOrderSummariesRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.OrderNumber,
|
||||
&i.Status,
|
||||
&i.TotalAmountCents,
|
||||
&i.Currency,
|
||||
&i.ShippingMethod,
|
||||
&i.ShippingPriceCents,
|
||||
&i.CreatedAt,
|
||||
&i.PaymentStatus,
|
||||
&i.ShippingStatus,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const summarizeOrderRevenue = `-- name: SummarizeOrderRevenue :one
|
||||
SELECT
|
||||
COUNT(*)::bigint AS order_count,
|
||||
COALESCE(SUM(total_amount_cents), 0)::bigint AS gross_revenue_cents,
|
||||
COALESCE(SUM(shipping_price_cents), 0)::bigint AS shipping_revenue_cents
|
||||
FROM eshop_orders
|
||||
WHERE deleted_at IS NULL
|
||||
AND created_at >= $1
|
||||
AND created_at < $2
|
||||
`
|
||||
|
||||
type SummarizeOrderRevenueParams struct {
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
CreatedAt_2 sql.NullTime `json:"created_at_2"`
|
||||
}
|
||||
|
||||
type SummarizeOrderRevenueRow struct {
|
||||
OrderCount int64 `json:"order_count"`
|
||||
GrossRevenueCents int64 `json:"gross_revenue_cents"`
|
||||
ShippingRevenueCents int64 `json:"shipping_revenue_cents"`
|
||||
}
|
||||
|
||||
func (q *Queries) SummarizeOrderRevenue(ctx context.Context, arg SummarizeOrderRevenueParams) (SummarizeOrderRevenueRow, error) {
|
||||
row := q.db.QueryRowContext(ctx, summarizeOrderRevenue, arg.CreatedAt, arg.CreatedAt_2)
|
||||
var i SummarizeOrderRevenueRow
|
||||
err := row.Scan(&i.OrderCount, &i.GrossRevenueCents, &i.ShippingRevenueCents)
|
||||
return i, err
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package eshopreporting
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type EshopCart struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
UserID sql.NullInt32 `json:"user_id"`
|
||||
SessionToken sql.NullString `json:"session_token"`
|
||||
Currency sql.NullString `json:"currency"`
|
||||
Completed sql.NullBool `json:"completed"`
|
||||
}
|
||||
|
||||
type EshopCartItem struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
CartID int32 `json:"cart_id"`
|
||||
ProductID int32 `json:"product_id"`
|
||||
VariantID sql.NullInt32 `json:"variant_id"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
UnitPriceCents int64 `json:"unit_price_cents"`
|
||||
Currency sql.NullString `json:"currency"`
|
||||
}
|
||||
|
||||
type EshopOrder struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
OrderNumber string `json:"order_number"`
|
||||
UserID sql.NullInt32 `json:"user_id"`
|
||||
SessionToken sql.NullString `json:"session_token"`
|
||||
Email sql.NullString `json:"email"`
|
||||
FirstName sql.NullString `json:"first_name"`
|
||||
LastName sql.NullString `json:"last_name"`
|
||||
BillingAddressJson sql.NullString `json:"billing_address_json"`
|
||||
ShippingAddressJson sql.NullString `json:"shipping_address_json"`
|
||||
Status sql.NullString `json:"status"`
|
||||
TotalAmountCents int64 `json:"total_amount_cents"`
|
||||
Currency sql.NullString `json:"currency"`
|
||||
ShippingMethod sql.NullString `json:"shipping_method"`
|
||||
ShippingPriceCents sql.NullInt64 `json:"shipping_price_cents"`
|
||||
ShippingDataJson sql.NullString `json:"shipping_data_json"`
|
||||
MetadataJson sql.NullString `json:"metadata_json"`
|
||||
}
|
||||
|
||||
type EshopOrderItem struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
OrderID int32 `json:"order_id"`
|
||||
ProductID int32 `json:"product_id"`
|
||||
VariantID sql.NullInt32 `json:"variant_id"`
|
||||
Name string `json:"name"`
|
||||
Sku sql.NullString `json:"sku"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
UnitPriceCents int64 `json:"unit_price_cents"`
|
||||
Currency sql.NullString `json:"currency"`
|
||||
VatRate sql.NullString `json:"vat_rate"`
|
||||
}
|
||||
|
||||
type EshopPayment struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
OrderID int32 `json:"order_id"`
|
||||
Provider sql.NullString `json:"provider"`
|
||||
ProviderPaymentID sql.NullString `json:"provider_payment_id"`
|
||||
Status sql.NullString `json:"status"`
|
||||
AmountCents int64 `json:"amount_cents"`
|
||||
Currency sql.NullString `json:"currency"`
|
||||
RawPayloadJson sql.NullString `json:"raw_payload_json"`
|
||||
}
|
||||
|
||||
type EshopProduct struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
ShortDescription sql.NullString `json:"short_description"`
|
||||
DescriptionHtml sql.NullString `json:"description_html"`
|
||||
PriceCents int64 `json:"price_cents"`
|
||||
Currency sql.NullString `json:"currency"`
|
||||
VatRate sql.NullString `json:"vat_rate"`
|
||||
Active sql.NullBool `json:"active"`
|
||||
StockMode sql.NullString `json:"stock_mode"`
|
||||
DefaultImageUrl sql.NullString `json:"default_image_url"`
|
||||
GalleryJson sql.NullString `json:"gallery_json"`
|
||||
Tags sql.NullString `json:"tags"`
|
||||
MetadataJson sql.NullString `json:"metadata_json"`
|
||||
CategoryID sql.NullInt32 `json:"category_id"`
|
||||
}
|
||||
|
||||
type EshopProductCategory struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
ParentID sql.NullInt32 `json:"parent_id"`
|
||||
DisplayOrder sql.NullInt32 `json:"display_order"`
|
||||
Active sql.NullBool `json:"active"`
|
||||
}
|
||||
|
||||
type EshopProductVariant struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
ProductID int32 `json:"product_id"`
|
||||
Sku sql.NullString `json:"sku"`
|
||||
Name sql.NullString `json:"name"`
|
||||
AttributesJson sql.NullString `json:"attributes_json"`
|
||||
StockQty sql.NullInt32 `json:"stock_qty"`
|
||||
Barcode sql.NullString `json:"barcode"`
|
||||
ImageUrl sql.NullString `json:"image_url"`
|
||||
}
|
||||
|
||||
type EshopSetting struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
DefaultCurrency sql.NullString `json:"default_currency"`
|
||||
SupportedCurrencies sql.NullString `json:"supported_currencies"`
|
||||
DefaultCountry sql.NullString `json:"default_country"`
|
||||
ShippingOptionsJson sql.NullString `json:"shipping_options_json"`
|
||||
TermsUrl sql.NullString `json:"terms_url"`
|
||||
ReturnsPolicyUrl sql.NullString `json:"returns_policy_url"`
|
||||
SupportEmail sql.NullString `json:"support_email"`
|
||||
SupportPhone sql.NullString `json:"support_phone"`
|
||||
}
|
||||
|
||||
type EshopShippingLabel struct {
|
||||
ID int32 `json:"id"`
|
||||
CreatedAt sql.NullTime `json:"created_at"`
|
||||
UpdatedAt sql.NullTime `json:"updated_at"`
|
||||
DeletedAt sql.NullTime `json:"deleted_at"`
|
||||
OrderID int32 `json:"order_id"`
|
||||
Carrier sql.NullString `json:"carrier"`
|
||||
PacketaPacketID sql.NullString `json:"packeta_packet_id"`
|
||||
TrackingNumber sql.NullString `json:"tracking_number"`
|
||||
LabelUrl sql.NullString `json:"label_url"`
|
||||
Status sql.NullString `json:"status"`
|
||||
HistoryJson sql.NullString `json:"history_json"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
|
||||
package eshopreporting
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
ListOrderStatusBreakdown(ctx context.Context) ([]ListOrderStatusBreakdownRow, error)
|
||||
ListRecentOrderSummaries(ctx context.Context, limit int32) ([]ListRecentOrderSummariesRow, error)
|
||||
SummarizeOrderRevenue(ctx context.Context, arg SummarizeOrderRevenueParams) (SummarizeOrderRevenueRow, error)
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
@@ -0,0 +1,121 @@
|
||||
package dbschema
|
||||
|
||||
import (
|
||||
"fotbal-club/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AllModels returns the complete set of persisted models used to bootstrap the schema.
|
||||
func AllModels() []interface{} {
|
||||
return []interface{}{
|
||||
&models.SetupInfo{},
|
||||
&models.ClubInfo{},
|
||||
&models.Settings{},
|
||||
&models.User{},
|
||||
&models.UserProfile{},
|
||||
&models.Article{},
|
||||
&models.Category{},
|
||||
&models.ArticleTeamLink{},
|
||||
&models.ArticleMatchLink{},
|
||||
&models.Team{},
|
||||
&models.Player{},
|
||||
&models.Club{},
|
||||
&models.Sponsor{},
|
||||
&models.Banner{},
|
||||
&models.ScoreboardState{},
|
||||
&models.ContactCategory{},
|
||||
&models.Contact{},
|
||||
&models.ContactMessage{},
|
||||
&models.NewsletterSubscription{},
|
||||
&models.PasswordReset{},
|
||||
&models.VisitorEvent{},
|
||||
&models.AboutPage{},
|
||||
&models.EmailLog{},
|
||||
&models.EmailEvent{},
|
||||
&models.NewsletterSentLog{},
|
||||
&models.MatchNotification{},
|
||||
&models.BlogNotification{},
|
||||
&models.MatchOverride{},
|
||||
&models.TeamLogoOverride{},
|
||||
&models.NavigationItem{},
|
||||
&models.SocialLink{},
|
||||
&models.PageElementConfig{},
|
||||
&models.ShortLink{},
|
||||
&models.LinkClick{},
|
||||
&models.Poll{},
|
||||
&models.PollOption{},
|
||||
&models.PollVote{},
|
||||
&models.Comment{},
|
||||
&models.CommentReaction{},
|
||||
&models.CommentBan{},
|
||||
&models.UnbanRequest{},
|
||||
&models.CommentReport{},
|
||||
&models.PointsTransaction{},
|
||||
&models.Achievement{},
|
||||
&models.UserAchievement{},
|
||||
&models.RewardItem{},
|
||||
&models.RewardRedemption{},
|
||||
&models.Sweepstake{},
|
||||
&models.SweepstakePrize{},
|
||||
&models.SweepstakeEntry{},
|
||||
&models.SweepstakeWinner{},
|
||||
&models.UploadedFile{},
|
||||
&models.FileUsage{},
|
||||
&models.ErrorEvent{},
|
||||
&models.CompetitionAlias{},
|
||||
&models.Clothing{},
|
||||
&models.Language{},
|
||||
&models.Translation{},
|
||||
&models.ContentTranslation{},
|
||||
&models.UserLanguagePreference{},
|
||||
&models.ManualCompetition{},
|
||||
&models.ManualMatch{},
|
||||
&models.ManualTableRow{},
|
||||
&models.Event{},
|
||||
&models.EventAttachment{},
|
||||
&models.QRCode{},
|
||||
&models.EshopProductCategory{},
|
||||
&models.EshopProduct{},
|
||||
&models.EshopProductVariant{},
|
||||
&models.EshopCart{},
|
||||
&models.EshopCartItem{},
|
||||
&models.EshopOrder{},
|
||||
&models.EshopOrderItem{},
|
||||
&models.EshopPayment{},
|
||||
&models.EshopShippingLabel{},
|
||||
&models.EshopSettings{},
|
||||
&models.Facility{},
|
||||
&models.FacilityAvailabilityRule{},
|
||||
&models.FacilityBooking{},
|
||||
&models.FacilityEquipment{},
|
||||
&models.FacilityMaintenance{},
|
||||
&models.WeatherCondition{},
|
||||
&models.FacilityBookingTemplate{},
|
||||
&models.Budget{},
|
||||
&models.Sponsorship{},
|
||||
&models.SponsorshipPayment{},
|
||||
&models.SponsorshipDocument{},
|
||||
&models.Expense{},
|
||||
&models.ExpenseDocument{},
|
||||
&models.FinancialReport{},
|
||||
&models.FinancialSettings{},
|
||||
&models.Invoice{},
|
||||
&models.InvoiceItem{},
|
||||
&models.InvoicePayment{},
|
||||
&models.InvoiceCustomer{},
|
||||
&models.InvoiceTemplate{},
|
||||
&models.InvoiceSettings{},
|
||||
&models.InvoiceSequence{},
|
||||
&models.AuditLog{},
|
||||
&models.TicketType{},
|
||||
&models.TicketCampaign{},
|
||||
&models.CampaignTicketType{},
|
||||
&models.Ticket{},
|
||||
&models.TicketAvailability{},
|
||||
}
|
||||
}
|
||||
|
||||
func AutoMigrate(db *gorm.DB) error {
|
||||
return db.AutoMigrate(AllModels()...)
|
||||
}
|
||||
Reference in New Issue
Block a user