diff --git a/http/webhook.go b/http/webhook.go index 4a9a3c1..3a8bcd1 100644 --- a/http/webhook.go +++ b/http/webhook.go @@ -1,9 +1,11 @@ package http import ( + "context" "crypto/hmac" "crypto/sha256" "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -11,13 +13,23 @@ import ( "net/http" "os" "os/exec" - "path" "strings" "github.com/quantonganh/blog" "github.com/quantonganh/blog/markdown" ) +type webhookPayload struct { + Commits []struct { + ID string `json:"id"` + Message string `json:"message"` + Timestamp string `json:"timestamp"` + Added []string `json:"added"` + Removed []string `json:"removed"` + Modified []string `json:"modified"` + } `json:"commits"` +} + func (s *Server) webhookHandler(config *blog.Config) appHandler { return func(w http.ResponseWriter, r *http.Request) error { body, err := io.ReadAll(r.Body) @@ -33,6 +45,16 @@ func (s *Server) webhookHandler(config *blog.Config) appHandler { } } + var payload webhookPayload + if err := json.Unmarshal(body, &payload); err != nil { + return err + } + + addedPosts, removedFiles, modifiedPosts, err := getChangedPosts(config, payload) + if err != nil { + return err + } + cmd := exec.Command("git", "-C", config.Posts.Dir, "pull") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -40,34 +62,14 @@ func (s *Server) webhookHandler(config *blog.Config) appHandler { return err } - posts, err := markdown.GetAllPosts(config.Posts.Dir) - if err != nil { + if err := s.reload(config, addedPosts, removedFiles, modifiedPosts); err != nil { return err } - s.reload(config, posts) return nil } } -func (s *Server) reload(config *blog.Config, posts []*blog.Post) { - if s.PostService != nil { - s.PostService = markdown.NewPostService(posts) - } - - if s.SearchService != nil { - indexPath := path.Join(path.Dir(config.Posts.Dir), path.Base(config.Posts.Dir)+".bleve") - searchService, err := markdown.NewSearchService(indexPath, posts) - if err != nil { - log.Printf("Failed to reload search service: %v\n", err) - return - } - s.SearchService = searchService - } - - log.Println("Content reloaded successfully.") -} - func verifySignature(signature string, payload []byte, secret string) error { parts := strings.SplitN(signature, "=", 2) if len(parts) != 2 { @@ -92,3 +94,103 @@ func verifySignature(signature string, payload []byte, secret string) error { return nil } + +func getChangedPosts(config *blog.Config, payload webhookPayload) ([]*blog.Post, []string, []*blog.Post, error) { + var ( + addedFiles []string + removedFiles []string + modifiedFiles []string + ) + for _, commit := range payload.Commits { + addedFiles = append(addedFiles, commit.Added...) + removedFiles = append(removedFiles, commit.Removed...) + modifiedFiles = append(modifiedFiles, commit.Modified...) + } + + var ( + addedPosts []*blog.Post + modifiedPosts []*blog.Post + ) + for _, name := range addedFiles { + f, err := os.Open(name) + if err != nil { + return nil, nil, nil, err + } + + newPost, err := markdown.Parse(context.Background(), config.Posts.Dir, f) + if err != nil { + return nil, nil, nil, err + } + + addedPosts = append(addedPosts, newPost) + } + + for _, name := range modifiedFiles { + f, err := os.Open(name) + if err != nil { + return nil, nil, nil, err + } + + modifiedPost, err := markdown.Parse(context.Background(), config.Posts.Dir, f) + if err != nil { + return nil, nil, nil, err + } + + modifiedPosts = append(modifiedPosts, modifiedPost) + } + + return addedPosts, removedFiles, modifiedPosts, nil +} + +func (s *Server) reload(config *blog.Config, addedPosts []*blog.Post, removedFiles []string, modifiedPosts []*blog.Post) error { + if s.PostService != nil { + posts := s.PostService.GetAllPosts() + updatedPosts, err := updatePosts(config, posts, addedPosts, removedFiles, modifiedPosts) + if err != nil { + return err + } + s.PostService = markdown.NewPostService(updatedPosts) + } + + if s.SearchService != nil { + index := s.SearchService.GetIndex() + batch := index.NewBatch() + + for _, name := range removedFiles { + if err := index.Delete(name); err != nil { + return err + } + } + + for _, post := range append(addedPosts, modifiedPosts...) { + if err := s.SearchService.Index(post, batch); err != nil { + return err + } + } + } + + log.Println("Content reloaded successfully.") + return nil +} + +func updatePosts(config *blog.Config, posts []*blog.Post, addedPosts []*blog.Post, removedFiles []string, modifiedPosts []*blog.Post) ([]*blog.Post, error) { + posts = append(posts, addedPosts...) + + for _, name := range removedFiles { + for i, post := range posts { + if post.URI == name { + posts = append(posts[:i], posts[i+1:]...) + } + } + } + + for _, p := range modifiedPosts { + for i, post := range posts { + if post.URI == p.URI { + posts[i] = p + } + } + } + + return posts, nil +} diff --git a/markdown/search.go b/markdown/search.go index 096f758..60ec87d 100644 --- a/markdown/search.go +++ b/markdown/search.go @@ -37,11 +37,14 @@ func NewSearchService(indexPath string, posts []*blog.Post) (blog.SearchService, } } + ss := &searchService{ + index: index, + } batch := index.NewBatch() for i, post := range posts { post.ID = i - if err := indexPost(post, batch); err != nil { + if err := ss.Index(post, batch); err != nil { return nil, err } } @@ -50,12 +53,14 @@ func NewSearchService(indexPath string, posts []*blog.Post) (blog.SearchService, return nil, errors.Wrapf(err, "failed to index batch") } - return &searchService{ - index: index, - }, nil + return ss, nil +} + +func (ss *searchService) GetIndex() bleve.Index { + return ss.index } -func indexPost(post *blog.Post, batch *bleve.Batch) error { +func (ss *searchService) Index(post *blog.Post, batch *bleve.Batch) error { doc := document.Document{ ID: post.URI, } diff --git a/post.go b/post.go index 3ad0775..b546cda 100644 --- a/post.go +++ b/post.go @@ -5,6 +5,7 @@ import ( "strconv" "time" + "github.com/blevesearch/bleve" "github.com/pkg/errors" ) @@ -36,6 +37,8 @@ type PostService interface { // SearchService is the interface that wraps methods related to search type SearchService interface { + GetIndex() bleve.Index + Index(*Post, *bleve.Batch) error Search(value string) ([]*Post, error) CloseIndex() error }