Skip to content

Commit 0bbabd9

Browse files
committed
feat: solid test feature
1 parent 750e931 commit 0bbabd9

File tree

8 files changed

+129
-30
lines changed

8 files changed

+129
-30
lines changed

main.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@ package main
33
import (
44
"fmt"
55
"net/http"
6+
"os"
67
"strconv"
78

89
"github.com/AstraBert/git-push-blog/models"
910
pagereader "github.com/AstraBert/git-push-blog/page_reader"
1011
"github.com/AstraBert/git-push-blog/templates"
12+
textsearch "github.com/AstraBert/git-push-blog/text_search"
1113
"github.com/a-h/templ"
1214
)
1315

16+
func pathExists(path string) bool {
17+
_, err := os.Stat(path)
18+
return !os.IsNotExist(err)
19+
}
20+
1421
func main() {
1522
mdFiles, errFls := pagereader.GetMarkdownFiles("./contents")
1623
if errFls != nil {
@@ -31,6 +38,15 @@ func main() {
3138
return
3239
}
3340
blogs = models.SortBlogPosts(blogs)
41+
if pathExists("posts.bleve") {
42+
os.RemoveAll("posts.bleve")
43+
}
44+
index, err := textsearch.CreateIndex(blogs, "posts.bleve")
45+
if err != nil {
46+
fmt.Println("An error occurred while creating the index: " + err.Error())
47+
return
48+
}
49+
3450
homeComponent := templates.Home()
3551
blogComponent := templates.BlogPage(blogs)
3652

@@ -52,7 +68,7 @@ func main() {
5268
var blogPost *models.BlogPost
5369

5470
for _, post := range blogs {
55-
if post.Id == postId {
71+
if post.Id == id {
5672
blogPost = post
5773
break
5874
}
@@ -61,6 +77,50 @@ func main() {
6177
templates.Post(blogPost).Render(r.Context(), w)
6278
})
6379

80+
http.HandleFunc("POST /search", func(w http.ResponseWriter, r *http.Request) {
81+
err := r.ParseForm()
82+
if err != nil {
83+
http.Error(w, err.Error(), http.StatusBadRequest)
84+
return
85+
}
86+
87+
searchText := r.FormValue("search")
88+
if searchText == "" {
89+
http.Error(w, "Search text is required", http.StatusBadRequest)
90+
return
91+
}
92+
93+
results, err := textsearch.SearchText(searchText, index)
94+
if err != nil {
95+
http.Error(w, err.Error(), http.StatusInternalServerError)
96+
return
97+
}
98+
99+
searchResults, errS := textsearch.ParseSearchResults(results)
100+
101+
if errS != nil {
102+
http.Error(w, "No matches where found for the search", http.StatusInternalServerError)
103+
return
104+
}
105+
106+
// Return HTML instead of JSON for HTMX
107+
w.Header().Set("Content-Type", "text/html")
108+
109+
// Generate HTML for search results
110+
html := ""
111+
for _, post := range searchResults {
112+
html += fmt.Sprintf(`<li class="text-gray-600 font-sans text-xl">• <a href="/blog/%s"><span class="text-pink-500 underline">%s</span></a> posted by <span class="font-semibold text-pink-400">%s</span> on %s</li>`,
113+
post.Id, post.Title, post.Author, post.PublishingDate)
114+
}
115+
116+
if html == "" {
117+
html = `<li class="text-gray-600 font-sans text-xl">No results found</li>`
118+
}
119+
120+
w.WriteHeader(http.StatusOK)
121+
w.Write([]byte(html))
122+
})
123+
64124
fmt.Println("Server started on :8000")
65125
http.ListenAndServe(":8000", nil)
66126
}

models/models.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,27 @@ package models
33
import (
44
"cmp"
55
"slices"
6+
"strconv"
67
)
78

89
type BlogPost struct {
9-
Id int `json:"id"`
10+
Id string `json:"id"`
1011
Title string `json:"title"`
1112
PublishingDate string `json:"publishing_date"`
1213
Author string `json:"author"`
1314
Content string `json:"content"`
1415
}
1516

16-
func NewBlogPost(id int, title, pubDate, author, content string) *BlogPost {
17+
func NewBlogPost(id string, title, pubDate, author, content string) *BlogPost {
1718
return &BlogPost{Id: id, Title: title, PublishingDate: pubDate, Author: author, Content: content}
1819
}
1920

2021
func SortBlogPosts(blogs []*BlogPost) []*BlogPost {
2122
slices.SortFunc(blogs,
2223
func(a, b *BlogPost) int {
23-
return cmp.Compare(b.Id, a.Id)
24+
bId, _ := strconv.Atoi(b.Id)
25+
aId, _ := strconv.Atoi(a.Id)
26+
return cmp.Compare(bId, aId)
2427
})
2528
return blogs
2629
}

models/models_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,36 @@ import (
77

88
func TestNewBlogPost(t *testing.T) {
99
testCases := []struct {
10-
id int
10+
id string
1111
title string
1212
author string
1313
publishDate string
1414
content string
1515
}{
16-
{1, "hello", "world", "2025-09-20", "hello world"},
17-
{2, "test", "test-author", "2025-09-20", "this is a test"},
18-
{3, "test-1", "test-author-1", "2025-09-20", "this is a test and even more..."},
16+
{"1", "hello", "world", "2025-09-20", "hello world"},
17+
{"2", "test", "test-author", "2025-09-20", "this is a test"},
18+
{"3", "test-1", "test-author-1", "2025-09-20", "this is a test and even more..."},
1919
}
2020
for _, tc := range testCases {
2121
post := NewBlogPost(tc.id, tc.title, tc.publishDate, tc.author, tc.content)
2222
if tc.author != post.Author || tc.id != post.Id || tc.content != post.Content || tc.publishDate != post.PublishingDate {
23-
t.Errorf("Expecting BlogPost{Id: %d, Author: %s, Content: %s, PublishingDate: %s, Title: %s}, got BlogPost{Id: %d, Author: %s, Content: %s, PublishingDate: %s, Title: %s}", tc.id, tc.author, tc.content, tc.publishDate, tc.title, post.Id, post.Author, post.Content, post.PublishingDate, post.Title)
23+
t.Errorf("Expecting BlogPost{Id: %s, Author: %s, Content: %s, PublishingDate: %s, Title: %s}, got BlogPost{Id: %s, Author: %s, Content: %s, PublishingDate: %s, Title: %s}", tc.id, tc.author, tc.content, tc.publishDate, tc.title, post.Id, post.Author, post.Content, post.PublishingDate, post.Title)
2424
}
2525
}
2626
}
2727

2828
func TestSortBlogPosts(t *testing.T) {
2929
testCases := []struct {
3030
startingList []*BlogPost
31-
expected []int
31+
expected []string
3232
}{
33-
{[]*BlogPost{NewBlogPost(1, "", "", "", ""), NewBlogPost(4, "", "", "", ""), NewBlogPost(2, "", "", "", ""), NewBlogPost(3, "", "", "", "")}, []int{4, 3, 2, 1}},
34-
{[]*BlogPost{NewBlogPost(5, "", "", "", ""), NewBlogPost(3, "", "", "", ""), NewBlogPost(2, "", "", "", ""), NewBlogPost(1, "", "", "", "")}, []int{5, 3, 2, 1}},
35-
{[]*BlogPost{NewBlogPost(5, "", "", "", ""), NewBlogPost(3, "", "", "", ""), NewBlogPost(1, "", "", "", ""), NewBlogPost(1, "", "", "", "")}, []int{5, 3, 1, 1}},
33+
{[]*BlogPost{NewBlogPost("1", "", "", "", ""), NewBlogPost("4", "", "", "", ""), NewBlogPost("2", "", "", "", ""), NewBlogPost("3", "", "", "", "")}, []string{"4", "3", "2", "1"}},
34+
{[]*BlogPost{NewBlogPost("5", "", "", "", ""), NewBlogPost("3", "", "", "", ""), NewBlogPost("2", "", "", "", ""), NewBlogPost("1", "", "", "", "")}, []string{"5", "3", "2", "1"}},
35+
{[]*BlogPost{NewBlogPost("5", "", "", "", ""), NewBlogPost("3", "", "", "", ""), NewBlogPost("1", "", "", "", ""), NewBlogPost("1", "", "", "", "")}, []string{"5", "3", "1", "1"}},
3636
}
3737
for _, tc := range testCases {
3838
blogs := SortBlogPosts(tc.startingList)
39-
blogIds := make([]int, len(blogs))
39+
blogIds := make([]string, len(blogs))
4040
for i, blog := range blogs {
4141
blogIds[i] = blog.Id
4242
}

page_reader/page_reader.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ func AddStyleToHTML(content string) string {
3131
}
3232

3333
func MarkdownToPost(filePath string) (*models.BlogPost, error) {
34-
postId, errConv := strconv.Atoi(strings.ReplaceAll(path.Base(filePath), ".md", ""))
35-
if errConv != nil {
36-
return nil, errConv
37-
}
34+
postId := strings.ReplaceAll(path.Base(filePath), ".md", "")
3835
var postTitle string
3936
var postAuthor string
4037
var postDate string

page_reader/page_reader_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"os"
55
"path"
66
"slices"
7-
"strconv"
87
"strings"
98
"testing"
109

@@ -100,9 +99,9 @@ func TestMarkdownToPost(t *testing.T) {
10099
t.Errorf("Expecting no error while processing %s, got %s", fl, err.Error())
101100
}
102101

103-
postId, _ := strconv.Atoi(strings.ReplaceAll(path.Base(fl), ".md", ""))
102+
postId := strings.ReplaceAll(path.Base(fl), ".md", "")
104103
if blog.Id != postId {
105-
t.Errorf("Expected blog.Id to be %d, got %d", postId, blog.Id)
104+
t.Errorf("Expected blog.Id to be %s, got %s", postId, blog.Id)
106105
}
107106
if blog.Author != "Clelia Astra Bertelli" {
108107
t.Errorf("Expected blog.Author to be 'Clelia Astra Bertelli', got '%s'", blog.Author)

templates/blog.templ

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ templ BlogPage(blogPosts []*models.BlogPost) {
2828
<a href="/blog" class="font-comic text-3xl text-pink-600">Explore the blog</a>
2929
</div>
3030
<div>
31-
<form>
31+
<form hx-post="/search" hx-target="#blogList" hx-trigger="submit">
3232
<input name="search" placeholder="Search Something" class="text-sans rounded-lg border border-gray-100 shadow-lg text-lg">
33-
<button class="bg-pink-600 text-white font-semibold font-sans w-[100px] py-3 px-6 rounded-lg shadow-xl">
33+
<button class="bg-pink-600 text-white font-semibold font-sans w-[100px] py-3 px-6 rounded-lg shadow-xl" type="submit">
3434
🔍
3535
</button>
3636
</form>
@@ -45,7 +45,7 @@ templ BlogPage(blogPosts []*models.BlogPost) {
4545
<div class="h-full w-full bg-white/15 rounded-2xl backdrop-filter backdrop-blur-xl border border-white/20 shadow-xl flex flex-col p-5">
4646
<ul id="blogList">
4747
for _, post := range blogPosts {
48-
<li class="text-gray-600 font-sans text-xl">• <a href={fmt.Sprintf("/blog/%d", post.Id)}><span class="text-pink-500 underline">{post.Title}</span></a> posted by <span class="font-semibold text-pink-400">{post.Author}</span> on {post.PublishingDate}</li>
48+
<li class="text-gray-600 font-sans text-xl">• <a href={fmt.Sprintf("/blog/%s", post.Id)}><span class="text-pink-500 underline">{post.Title}</span></a> posted by <span class="font-semibold text-pink-400">{post.Author}</span> on {post.PublishingDate}</li>
4949
}
5050
</ul>
5151
</div>

templates/blog_templ.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

text_search/text_search.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,21 @@ package textsearch
6161
// }
6262

6363
import (
64+
"errors"
65+
6466
"github.com/AstraBert/git-push-blog/models"
6567
"github.com/blevesearch/bleve"
6668
)
6769

68-
func CreateIndex(posts []*models.BlogPost) (bleve.Index, error) {
70+
func CreateIndex(posts []*models.BlogPost, path string) (bleve.Index, error) {
6971
mapping := bleve.NewIndexMapping()
70-
index, err := bleve.New("posts.bleve", mapping)
72+
index, err := bleve.New(path, mapping)
7173
if err != nil {
7274
return nil, err
7375
}
7476
batch := index.NewBatch()
7577
for _, post := range posts {
76-
err := batch.Index(post.Title, post)
78+
err := batch.Index(post.Id, post)
7779
if err != nil {
7880
continue
7981
}
@@ -91,10 +93,48 @@ func SearchText(text string, index bleve.Index) (*bleve.SearchResult, error) {
9193
query := bleve.NewQueryStringQuery(text)
9294
req := bleve.NewSearchRequest(query)
9395
req.Explain = false
94-
req.Fields = []string{"title", "author", "content"}
96+
req.Fields = []string{"title", "author", "publishing_date"}
9597
searchResult, err := index.Search(req)
9698
if err != nil {
9799
return nil, err
98100
}
99101
return searchResult, nil
100102
}
103+
104+
func ParseSearchResults(searchResults *bleve.SearchResult) ([]*models.BlogPost, error) {
105+
var posts []*models.BlogPost
106+
107+
for _, hit := range searchResults.Hits {
108+
post := &models.BlogPost{}
109+
110+
// Access fields directly from the hit
111+
if title, ok := hit.Fields["title"].(string); ok {
112+
post.Title = title
113+
} else {
114+
continue
115+
}
116+
117+
if author, ok := hit.Fields["author"].(string); ok {
118+
post.Author = author
119+
} else {
120+
continue
121+
}
122+
123+
if publishingDate, ok := hit.Fields["publishing_date"].(string); ok {
124+
post.PublishingDate = publishingDate
125+
} else {
126+
continue
127+
}
128+
129+
// You can also get the document ID if you stored it
130+
post.Id = hit.ID
131+
132+
posts = append(posts, post)
133+
}
134+
135+
if len(posts) == 0 {
136+
return nil, errors.New("no posts matched the search")
137+
}
138+
139+
return posts, nil
140+
}

0 commit comments

Comments
 (0)