309 lines
7.7 KiB
Go
309 lines
7.7 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitblog/internal/cache"
|
|
"gitblog/internal/config"
|
|
"gitblog/internal/content"
|
|
"gitblog/internal/github"
|
|
"gitblog/internal/templates"
|
|
|
|
"github.com/gorilla/handlers"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
type Server struct {
|
|
config *config.Config
|
|
githubClient *github.Client
|
|
contentMgr *content.Manager
|
|
cacheMgr *cache.Manager
|
|
templateMgr *templates.Manager
|
|
updateTicker *time.Ticker
|
|
}
|
|
|
|
func New(cfg *config.Config) *Server {
|
|
githubClient := github.NewClient(cfg.GitHubToken, cfg.GitHubOwner, cfg.GitHubRepo)
|
|
contentMgr := content.NewManager(githubClient)
|
|
cacheMgr := cache.NewManager(cfg.CacheDuration, 5*time.Minute)
|
|
templateMgr := templates.NewManager()
|
|
|
|
return &Server{
|
|
config: cfg,
|
|
githubClient: githubClient,
|
|
contentMgr: contentMgr,
|
|
cacheMgr: cacheMgr,
|
|
templateMgr: templateMgr,
|
|
}
|
|
}
|
|
|
|
func (s *Server) Start() error {
|
|
if err := s.templateMgr.LoadTemplates(); err != nil {
|
|
return fmt.Errorf("failed to load templates: %w", err)
|
|
}
|
|
|
|
if err := s.contentMgr.LoadContent(); err != nil {
|
|
return fmt.Errorf("failed to load content: %w", err)
|
|
}
|
|
|
|
s.startContentUpdater()
|
|
|
|
router := s.setupRoutes()
|
|
|
|
corsHandler := handlers.CORS(
|
|
handlers.AllowedOrigins([]string{"*"}),
|
|
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
|
|
handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
|
|
)(router)
|
|
|
|
loggedRouter := handlers.LoggingHandler(log.Writer(), corsHandler)
|
|
|
|
server := &http.Server{
|
|
Addr: ":" + s.config.Port,
|
|
Handler: loggedRouter,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 15 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
return server.ListenAndServe()
|
|
}
|
|
|
|
func (s *Server) setupRoutes() *mux.Router {
|
|
router := mux.NewRouter()
|
|
|
|
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("web/static/"))))
|
|
|
|
router.HandleFunc("/", s.handleHome).Methods("GET")
|
|
router.HandleFunc("/post/{slug}", s.handlePost).Methods("GET")
|
|
router.HandleFunc("/category/{category}", s.handleCategory).Methods("GET")
|
|
router.HandleFunc("/page/{path:.*}", s.handlePage).Methods("GET")
|
|
router.HandleFunc("/rss.xml", s.handleRSS).Methods("GET")
|
|
router.HandleFunc("/api/update", s.handleUpdate).Methods("POST")
|
|
|
|
router.NotFoundHandler = http.HandlerFunc(s.handle404)
|
|
|
|
return router
|
|
}
|
|
|
|
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) {
|
|
theme := s.getTheme(r)
|
|
|
|
posts, found := s.cacheMgr.GetPosts()
|
|
if !found {
|
|
posts = s.contentMgr.GetAllPosts()
|
|
s.cacheMgr.SetPosts(posts, s.config.CacheDuration)
|
|
}
|
|
|
|
data := templates.PageData{
|
|
Title: "Home",
|
|
Posts: posts,
|
|
Navigation: s.contentMgr.GetNavigation(),
|
|
Theme: theme,
|
|
CurrentYear: time.Now().Year(),
|
|
}
|
|
|
|
html, err := s.templateMgr.Render("home", data)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
func (s *Server) handlePost(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
slug := vars["slug"]
|
|
theme := s.getTheme(r)
|
|
|
|
post, found := s.cacheMgr.GetPost(slug)
|
|
if !found {
|
|
post, found = s.contentMgr.GetPost(slug)
|
|
if !found {
|
|
s.handle404(w, r)
|
|
return
|
|
}
|
|
s.cacheMgr.SetPost(slug, post, s.config.CacheDuration)
|
|
}
|
|
|
|
data := templates.PageData{
|
|
Title: post.Title,
|
|
Post: post,
|
|
Navigation: s.contentMgr.GetNavigation(),
|
|
Theme: theme,
|
|
CurrentYear: time.Now().Year(),
|
|
Meta: map[string]string{
|
|
"description": post.Excerpt,
|
|
},
|
|
}
|
|
|
|
html, err := s.templateMgr.Render("post", data)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
func (s *Server) handleCategory(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
category := vars["category"]
|
|
theme := s.getTheme(r)
|
|
|
|
posts, found := s.cacheMgr.GetPostsByCategory(category)
|
|
if !found {
|
|
posts = s.contentMgr.GetPostsByCategory(category)
|
|
s.cacheMgr.SetPostsByCategory(category, posts, s.config.CacheDuration)
|
|
}
|
|
|
|
data := templates.PageData{
|
|
Title: strings.Title(category),
|
|
Posts: posts,
|
|
Navigation: s.contentMgr.GetNavigation(),
|
|
Theme: theme,
|
|
CurrentYear: time.Now().Year(),
|
|
}
|
|
|
|
html, err := s.templateMgr.Render("category", data)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
func (s *Server) handlePage(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
path := vars["path"]
|
|
theme := s.getTheme(r)
|
|
|
|
page, found := s.cacheMgr.GetPage(path)
|
|
if !found {
|
|
page, found = s.contentMgr.GetPage(path)
|
|
if !found {
|
|
s.handle404(w, r)
|
|
return
|
|
}
|
|
s.cacheMgr.SetPage(path, page, s.config.CacheDuration)
|
|
}
|
|
|
|
data := templates.PageData{
|
|
Title: page.Title,
|
|
Page: page,
|
|
Navigation: s.contentMgr.GetNavigation(),
|
|
Theme: theme,
|
|
CurrentYear: time.Now().Year(),
|
|
}
|
|
|
|
html, err := s.templateMgr.Render("page", data)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
func (s *Server) handleRSS(w http.ResponseWriter, r *http.Request) {
|
|
posts := s.contentMgr.GetAllPosts()
|
|
|
|
rss := `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss version="2.0">
|
|
<channel>
|
|
<title>GitBlog</title>
|
|
<description>A modern blog powered by GitHub</description>
|
|
<link>` + s.config.BaseURL + `</link>
|
|
<lastBuildDate>` + time.Now().Format(time.RFC1123Z) + `</lastBuildDate>
|
|
`
|
|
|
|
for _, post := range posts {
|
|
rss += ` <item>
|
|
<title>` + post.Title + `</title>
|
|
<description><![CDATA[` + post.Excerpt + `]]></description>
|
|
<link>` + s.config.BaseURL + `/post/` + post.Slug + `</link>
|
|
<pubDate>` + post.Date.Format(time.RFC1123Z) + `</pubDate>
|
|
</item>
|
|
`
|
|
}
|
|
|
|
rss += `</channel>
|
|
</rss>`
|
|
|
|
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
|
|
w.Write([]byte(rss))
|
|
}
|
|
|
|
func (s *Server) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
|
if err := s.contentMgr.LoadContent(); err != nil {
|
|
http.Error(w, "Failed to update content", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
s.cacheMgr.Clear()
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("Content updated successfully"))
|
|
}
|
|
|
|
func (s *Server) handle404(w http.ResponseWriter, r *http.Request) {
|
|
theme := s.getTheme(r)
|
|
|
|
data := templates.PageData{
|
|
Title: "404 - Page Not Found",
|
|
Navigation: s.contentMgr.GetNavigation(),
|
|
Theme: theme,
|
|
CurrentYear: time.Now().Year(),
|
|
}
|
|
|
|
html, err := s.templateMgr.Render("404", data)
|
|
if err != nil {
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
func (s *Server) getTheme(r *http.Request) string {
|
|
cookie, err := r.Cookie("theme")
|
|
if err != nil {
|
|
return s.config.Theme
|
|
}
|
|
return cookie.Value
|
|
}
|
|
|
|
func (s *Server) startContentUpdater() {
|
|
s.updateTicker = time.NewTicker(s.config.UpdateInterval)
|
|
|
|
go func() {
|
|
for range s.updateTicker.C {
|
|
lastCommit, err := s.githubClient.GetLastCommit()
|
|
if err != nil {
|
|
log.Printf("Failed to check for updates: %v", err)
|
|
continue
|
|
}
|
|
|
|
if lastCommit.Commit.Committer.Date.After(s.contentMgr.GetLastUpdate()) {
|
|
log.Println("Content update detected, reloading...")
|
|
if err := s.contentMgr.LoadContent(); err != nil {
|
|
log.Printf("Failed to reload content: %v", err)
|
|
continue
|
|
}
|
|
s.cacheMgr.Clear()
|
|
}
|
|
}
|
|
}()
|
|
}
|