gitblog1
This commit is contained in:
308
internal/server/server.go
Normal file
308
internal/server/server.go
Normal file
@@ -0,0 +1,308 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
Reference in New Issue
Block a user