mirror of
https://github.com/Dvorinka/bizoni.git
synced 2026-06-03 18:22:57 +00:00
new sponsor and fix backend
This commit is contained in:
+56
-14
@@ -538,20 +538,17 @@ func extractSlug(path, filename string) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
s := string(b)
|
return extractSlugFromContent(string(b))
|
||||||
// Try to find slug in meta tag first
|
}
|
||||||
|
|
||||||
|
// extractSlugFromContent extracts the slug from HTML content string
|
||||||
|
func extractSlugFromContent(htmlContent string) string {
|
||||||
re := regexp.MustCompile(`(?is)<meta name="slug" content="([^"]+)"`)
|
re := regexp.MustCompile(`(?is)<meta name="slug" content="([^"]+)"`)
|
||||||
m := re.FindStringSubmatch(s)
|
m := re.FindStringSubmatch(htmlContent)
|
||||||
if len(m) >= 2 {
|
if len(m) >= 2 {
|
||||||
return m[1]
|
return m[1]
|
||||||
}
|
}
|
||||||
// Fallback: generate slug from title
|
return ""
|
||||||
title := extractTitle(path)
|
|
||||||
if title != "" {
|
|
||||||
return generateSlug(title)
|
|
||||||
}
|
|
||||||
// Final fallback: use filename without extension
|
|
||||||
return strings.TrimSuffix(filename, ".html")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractBlogID(path, filename string) string {
|
func extractBlogID(path, filename string) string {
|
||||||
@@ -591,6 +588,7 @@ func listLatestBlogs(siteRoot string, limit int) ([]BlogItem, error) {
|
|||||||
}
|
}
|
||||||
// Match both numeric (0001.html) and slug-based filenames
|
// Match both numeric (0001.html) and slug-based filenames
|
||||||
re := regexp.MustCompile(`^(\d{4}|[a-z0-9-]+)\.html$`)
|
re := regexp.MustCompile(`^(\d{4}|[a-z0-9-]+)\.html$`)
|
||||||
|
numericRe := regexp.MustCompile(`^\d{4}$`)
|
||||||
var items []BlogItem
|
var items []BlogItem
|
||||||
seenIDs := make(map[string]bool) // Track seen IDs to avoid duplicates
|
seenIDs := make(map[string]bool) // Track seen IDs to avoid duplicates
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
@@ -613,6 +611,13 @@ func listLatestBlogs(siteRoot string, limit int) ([]BlogItem, error) {
|
|||||||
|
|
||||||
// Mark this ID as seen
|
// Mark this ID as seen
|
||||||
seenIDs[id] = true
|
seenIDs[id] = true
|
||||||
|
// Also mark slug/numeric counterpart to prevent duplicates
|
||||||
|
if slug != "" && slug != id {
|
||||||
|
seenIDs[slug] = true
|
||||||
|
}
|
||||||
|
if numericRe.MatchString(id) && slug != "" {
|
||||||
|
seenIDs[slug] = true
|
||||||
|
}
|
||||||
// Determine mod time - prefer image modtime if exists, else html
|
// Determine mod time - prefer image modtime if exists, else html
|
||||||
mtime := time.Time{}
|
mtime := time.Time{}
|
||||||
htmlInfo, err1 := os.Stat(filepath.Join(blogDir, name))
|
htmlInfo, err1 := os.Stat(filepath.Join(blogDir, name))
|
||||||
@@ -1370,14 +1375,51 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
id := strings.TrimSpace(r.URL.Query().Get("id"))
|
id := strings.TrimSpace(r.URL.Query().Get("id"))
|
||||||
re := regexp.MustCompile(`^\d{4}$`)
|
// Accept numeric ID (0001) or slug (my-post-title)
|
||||||
if !re.MatchString(id) {
|
validID := regexp.MustCompile(`^\d{4}$`).MatchString(id) || regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(id)
|
||||||
|
if !validID {
|
||||||
http.Error(w, "invalid id", http.StatusBadRequest)
|
http.Error(w, "invalid id", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
site := staticPath()
|
site := staticPath()
|
||||||
_ = os.Remove(filepath.Join(site, "blog", id+".html"))
|
blogDir := filepath.Join(site, "blog")
|
||||||
_ = os.Remove(filepath.Join(site, "img", "blog", id+".png"))
|
|
||||||
|
// If numeric ID, also find and delete the slug file
|
||||||
|
if regexp.MustCompile(`^\d{4}$`).MatchString(id) {
|
||||||
|
// Delete numeric HTML file
|
||||||
|
numericPath := filepath.Join(blogDir, id+".html")
|
||||||
|
// Find slug before deleting
|
||||||
|
if content, err := os.ReadFile(numericPath); err == nil {
|
||||||
|
slug := extractSlugFromContent(string(content))
|
||||||
|
if slug != "" && slug != id {
|
||||||
|
_ = os.Remove(filepath.Join(blogDir, slug+".html"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = os.Remove(numericPath)
|
||||||
|
// Delete image
|
||||||
|
_ = os.Remove(filepath.Join(site, "img", "blog", id+".png"))
|
||||||
|
} else {
|
||||||
|
// It's a slug - find the numeric ID and delete both
|
||||||
|
entries, _ := os.ReadDir(blogDir)
|
||||||
|
for _, e := range entries {
|
||||||
|
name := e.Name()
|
||||||
|
if !regexp.MustCompile(`^\d{4}\.html$`).MatchString(name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
numericID := strings.TrimSuffix(name, ".html")
|
||||||
|
numericPath := filepath.Join(blogDir, name)
|
||||||
|
if content, err := os.ReadFile(numericPath); err == nil {
|
||||||
|
fileSlug := extractSlugFromContent(string(content))
|
||||||
|
if fileSlug == id {
|
||||||
|
// Found matching numeric file, delete both
|
||||||
|
_ = os.Remove(numericPath)
|
||||||
|
_ = os.Remove(filepath.Join(site, "img", "blog", numericID+".png"))
|
||||||
|
_ = os.Remove(filepath.Join(blogDir, id+".html"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
// Migration script to clean up duplicate blog files
|
||||||
|
// Run with: go run migrate_blogs.go <blog_directory>
|
||||||
|
//
|
||||||
|
// This script:
|
||||||
|
// 1. Scans all blog HTML files
|
||||||
|
// 2. Groups numeric files with their slug counterparts
|
||||||
|
// 3. Removes orphan slug files (slugs without matching numeric files)
|
||||||
|
// 4. Reports duplicates for manual review
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fmt.Println("Usage: go run migrate_blogs.go <blog_directory>")
|
||||||
|
fmt.Println("Example: go run migrate_blogs.go ../blog")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
blogDir := os.Args[1]
|
||||||
|
|
||||||
|
// Check directory exists
|
||||||
|
if _, err := os.Stat(blogDir); os.IsNotExist(err) {
|
||||||
|
fmt.Printf("Error: blog directory not found: %s\n", blogDir)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all files
|
||||||
|
entries, err := os.ReadDir(blogDir)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading directory: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
numericRe := regexp.MustCompile(`^\d{4}\.html$`)
|
||||||
|
slugRe := regexp.MustCompile(`^[a-z0-9-]+\.html$`)
|
||||||
|
|
||||||
|
// Map: numeric ID -> slug (extracted from file content)
|
||||||
|
numericToSlug := make(map[string]string)
|
||||||
|
// Map: slug -> numeric ID (extracted by matching content)
|
||||||
|
slugToNumeric := make(map[string]string)
|
||||||
|
// List of orphan slug files (no matching numeric file)
|
||||||
|
orphanSlugs := []string{}
|
||||||
|
// List of numeric files
|
||||||
|
numericFiles := []string{}
|
||||||
|
// List of slug files
|
||||||
|
slugFiles := []string{}
|
||||||
|
|
||||||
|
// First pass: categorize files
|
||||||
|
for _, e := range entries {
|
||||||
|
name := e.Name()
|
||||||
|
if numericRe.MatchString(name) {
|
||||||
|
numericFiles = append(numericFiles, name)
|
||||||
|
} else if slugRe.MatchString(name) {
|
||||||
|
slugFiles = append(slugFiles, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Found %d numeric files and %d slug files\n", len(numericFiles), len(slugFiles))
|
||||||
|
|
||||||
|
// Extract slugs from numeric files
|
||||||
|
for _, name := range numericFiles {
|
||||||
|
path := filepath.Join(blogDir, name)
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slug := extractSlugFromContent(string(content))
|
||||||
|
id := strings.TrimSuffix(name, ".html")
|
||||||
|
if slug != "" {
|
||||||
|
numericToSlug[id] = slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check slug files for matches
|
||||||
|
for _, name := range slugFiles {
|
||||||
|
slug := strings.TrimSuffix(name, ".html")
|
||||||
|
foundMatch := false
|
||||||
|
|
||||||
|
// Check if any numeric file has this slug
|
||||||
|
for numericID, numericSlug := range numericToSlug {
|
||||||
|
if numericSlug == slug {
|
||||||
|
slugToNumeric[slug] = numericID
|
||||||
|
foundMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundMatch {
|
||||||
|
orphanSlugs = append(orphanSlugs, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report findings
|
||||||
|
fmt.Println("\n=== Blog Migration Report ===")
|
||||||
|
fmt.Printf("\nNumeric files with slugs:\n")
|
||||||
|
for id, slug := range numericToSlug {
|
||||||
|
fmt.Printf(" %s -> %s\n", id, slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\nSlug files with matching numeric:\n")
|
||||||
|
for slug, id := range slugToNumeric {
|
||||||
|
fmt.Printf(" %s.html -> %s.html\n", slug, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(orphanSlugs) > 0 {
|
||||||
|
fmt.Printf("\nOrphan slug files (no matching numeric file):\n")
|
||||||
|
for _, name := range orphanSlugs {
|
||||||
|
fmt.Printf(" %s\n", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\nRemove %d orphan slug files? (y/n): ", len(orphanSlugs))
|
||||||
|
var response string
|
||||||
|
fmt.Scanln(&response)
|
||||||
|
if strings.ToLower(response) == "y" {
|
||||||
|
for _, name := range orphanSlugs {
|
||||||
|
path := filepath.Join(blogDir, name)
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
fmt.Printf(" Error removing %s: %v\n", name, err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Removed: %s\n", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Migration complete!")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Migration cancelled.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("\nNo orphan slug files found. Blog directory is clean.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractSlugFromContent(htmlContent string) string {
|
||||||
|
re := regexp.MustCompile(`(?is)<meta name="slug" content="([^"]+)"`)
|
||||||
|
m := re.FindStringSubmatch(htmlContent)
|
||||||
|
if len(m) >= 2 {
|
||||||
|
return m[1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -1857,6 +1857,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sxl-3 col-lg-3 col-md-6 col-sm-6 col-ms-6 col-xs-12 partners-wrap center-flex">
|
||||||
|
<div class="partners-item item center-flex">
|
||||||
|
<a href="https://sbernesurovinyuh.cz/" target="_blank">
|
||||||
|
<img decoding="async" src="img/sponzor29.png" class="image">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -547,6 +547,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-xl-3 col-lg-3 col-md-6 col-sm-6 col-ms-6 col-xs-12 partners-wrap center-flex">
|
||||||
|
<div class="partners-item item center-flex">
|
||||||
|
<a href="https://sbernesurovinyuh.cz/" target="_blank">
|
||||||
|
<img decoding="async" src="img/sponzor29.png" class="image">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user