Skip to content

Commit de4309b

Browse files
authored
Merge pull request #13 from LeetaoGoooo/feat-query
feat: tags page && and search
2 parents 31b3425 + 3ad06df commit de4309b

File tree

10 files changed

+250
-12
lines changed

10 files changed

+250
-12
lines changed

core/api.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package core
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
7+
"strings"
68

79
"github.com/shurcooL/githubv4"
810
"golang.org/x/oauth2"
@@ -126,3 +128,81 @@ func (api *BlogApi) FetchCategories(before string, after string) (Categories, er
126128

127129
return categories, err
128130
}
131+
132+
// FetchPostsByLabel 根据 label 和 category 获取对应的 posts
133+
func (api *BlogApi) QueryPosts(keyword string, label string, categories []string) (SearchResults, error) {
134+
var q struct {
135+
Search struct {
136+
PageInfo PageInfo
137+
Nodes []struct {
138+
Node `graphql:"... on Discussion"`
139+
}
140+
} `graphql:"search(query: $query first: $first, type: $type)"`
141+
}
142+
143+
var query = fmt.Sprintf("repo:%s/%s ", api.owner, api.repo)
144+
145+
if len(strings.Trim(keyword, "")) != 0 {
146+
query = fmt.Sprintf("%s %s ", query, keyword)
147+
}
148+
149+
if len(strings.Trim(label, "")) != 0 {
150+
query = fmt.Sprintf("%s label:\"%s\" ", query, label)
151+
}
152+
153+
if len(categories) != 0 {
154+
for _, category := range categories {
155+
query = fmt.Sprintf("%s category:\"%s\" ", query, category)
156+
}
157+
}
158+
159+
binds := map[string]interface{}{
160+
"first": githubv4.Int(PER_PAGE_POST_COUNT),
161+
"query": githubv4.String(query),
162+
"type": githubv4.SearchTypeDiscussion,
163+
"label_first": githubv4.Int(LABEL_MAX_COUNT),
164+
}
165+
166+
err := api.client.Query(context.Background(), &q, binds)
167+
if err != nil {
168+
return SearchResults{}, err
169+
}
170+
var posts []Node
171+
for _, node := range q.Search.Nodes {
172+
posts = append(posts, node.Node)
173+
}
174+
return SearchResults{
175+
PageInfo: q.Search.PageInfo,
176+
Nodes: posts,
177+
}, nil
178+
}
179+
180+
// fetch all labels from discussion 获取所有的 labels
181+
func (api *BlogApi) FetchAllLabels() ([]Label, error) {
182+
var q struct {
183+
Reposity struct {
184+
Labels struct {
185+
Edges []struct {
186+
Node Label
187+
}
188+
} `graphql:"labels(first: $first)"`
189+
} `graphql:"repository(owner: $owner, name: $repo)"`
190+
}
191+
192+
binds := map[string]interface{}{
193+
"first": githubv4.Int(MAX_LABELS_COUNT),
194+
"owner": githubv4.String(api.owner),
195+
"repo": githubv4.String(api.repo),
196+
}
197+
198+
err := api.client.Query(context.Background(), &q, binds)
199+
if err != nil {
200+
return []Label{}, err
201+
}
202+
203+
var labels []Label
204+
for _, node := range q.Reposity.Labels.Edges {
205+
labels = append(labels, node.Node)
206+
}
207+
return labels, nil
208+
}

core/api_test.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,54 @@
11
package core
22

3-
import "testing"
3+
import (
4+
"os"
5+
"testing"
6+
)
47

58
func TestApi(t *testing.T) {
6-
api := NewApi("username", "repo", "token")
9+
api := NewApi(os.Getenv("PURE_USER_NAME"), os.Getenv("PURE_REPO"), os.Getenv("PURE_TOKEN"))
710
t.Run("TestApiCategories", func(t *testing.T) {
811
categories, err := api.FetchCategories("", "")
912
if err != nil {
1013
t.Errorf("FetchCategories error: %v", err)
1114
}
1215
t.Logf("categories: %v", categories)
1316
})
17+
18+
t.Run("TestApiQuery", func(t *testing.T) {
19+
var label = "python"
20+
var keyword = "django"
21+
queryTestCases := []struct {
22+
keyword string
23+
label string
24+
categories []string
25+
resultCount int
26+
}{
27+
{keyword, "", []string{"随笔"}, 3},
28+
{"", label, []string{"随笔", "历史存档"}, 10},
29+
{keyword, label, []string{"随笔", "历史存档"}, 4},
30+
}
31+
32+
for _, queryTestCase := range queryTestCases {
33+
result, err := api.QueryPosts(queryTestCase.keyword, queryTestCase.label, queryTestCase.categories)
34+
posts := result.Nodes
35+
if err != nil {
36+
t.Errorf("QueryPosts error: %v", err)
37+
}
38+
if len(posts) != queryTestCase.resultCount {
39+
t.Errorf("QueryPosts failed, %v the number of results are supposed to be %d, but got %d\n", queryTestCase, queryTestCase.resultCount, len(posts))
40+
}
41+
}
42+
})
43+
44+
t.Run("TestApiLabels", func(t *testing.T) {
45+
labels, err := api.FetchAllLabels()
46+
if err != nil {
47+
t.Errorf("FetchCategories error: %v", err)
48+
}
49+
if len(labels) == 0 {
50+
t.Errorf("FetchCategories the number of labels is zero!")
51+
}
52+
t.Logf("labels: %v", labels)
53+
})
1454
}

core/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ const PER_PAGE_POST_COUNT = 10
88

99
// 最大的 category 数量
1010
const CATEGORY_MAX_COUNT = 10
11+
12+
// 最大的 labels 数量
13+
const MAX_LABELS_COUNT = 100

core/model.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,8 @@ type Repies struct {
7171
PageInfo PageInfo `json:"page_info,omitempty"`
7272
Nodes []Comment `json:"nodes,omitempty"`
7373
}
74+
75+
type SearchResults struct {
76+
PageInfo PageInfo `json:"page_info,omitempty"`
77+
Nodes []Node `json:"nodes,omitempty"`
78+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ require (
3636
github.com/gin-gonic/gin v1.8.1
3737
github.com/gorilla/feeds v1.1.1
3838
github.com/gosimple/slug v1.13.1
39-
github.com/shurcooL/githubv4 v0.0.0-20220922232305-70b4d362a8cb
39+
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc
4040
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783
4141
gopkg.in/yaml.v2 v2.4.0
4242
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
103103
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
104104
github.com/shurcooL/githubv4 v0.0.0-20220922232305-70b4d362a8cb h1:Ptg7eUGaD22iZMracv+h7ghDJkGaeQ1FQ9BnkRB6DOo=
105105
github.com/shurcooL/githubv4 v0.0.0-20220922232305-70b4d362a8cb/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
106+
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc h1:vH0NQbIDk+mJLvBliNGfcQgUmhlniWBDXC79oRxfZA0=
107+
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
106108
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc=
107109
github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
108110
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

main.go

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ type PageQuery struct {
3535
CategoryName string `uri:"category_name"`
3636
}
3737

38+
type SearchQuery struct {
39+
Keyword string `form:"keyword,omitempty"`
40+
Label string `form:"label,omitempty"`
41+
Categories []string `form:"categories,omitempty"`
42+
}
43+
3844
type PostQuery struct {
3945
Id uint64 `uri:"id" binding:"required"`
4046
Title string `uri:"title" binding:"required"`
@@ -135,9 +141,46 @@ func FetchPosts(c *gin.Context) {
135141
}
136142

137143
c.HTML(http.StatusOK, "index.html", map[string]any{
138-
"Posts": discussions,
139-
"Navbars": config.Categories,
140-
"About": config.About,
144+
"Title": pageQuery.CategoryName,
145+
"Nodes": discussions.Nodes,
146+
"PageInfo": discussions.PageInfo,
147+
"Navbars": config.Categories,
148+
"About": config.About,
149+
})
150+
}
151+
152+
func SearchPosts(c *gin.Context) {
153+
var searchQuery SearchQuery
154+
155+
if err := c.ShouldBind(&searchQuery); err != nil {
156+
c.HTML(http.StatusBadRequest, "error.html", map[string]any{
157+
"Message": err.Error(),
158+
})
159+
return
160+
}
161+
162+
if searchQuery.Label == "" && len(searchQuery.Categories) == 0 && searchQuery.Keyword == "" {
163+
c.HTML(http.StatusBadRequest, "error.html", map[string]any{
164+
"Message": "Invalid Params",
165+
})
166+
return
167+
}
168+
169+
result, err := api.QueryPosts(searchQuery.Keyword, searchQuery.Label, searchQuery.Categories)
170+
171+
if err != nil {
172+
c.HTML(http.StatusBadRequest, "error.html", map[string]any{
173+
"Message": err.Error(),
174+
})
175+
return
176+
}
177+
178+
c.HTML(http.StatusOK, "index.html", map[string]any{
179+
"Title": "Search Result",
180+
"Nodes": result.Nodes,
181+
"PageInfo": result.PageInfo,
182+
"Navbars": config.Categories,
183+
"About": config.About,
141184
})
142185
}
143186

@@ -167,6 +210,21 @@ func FetchPost(c *gin.Context) {
167210
})
168211
}
169212

213+
func TagPage(c *gin.Context) {
214+
labels, err := api.FetchAllLabels()
215+
if err != nil {
216+
c.HTML(http.StatusBadRequest, "error.html", map[string]any{
217+
"Message": err.Error(),
218+
})
219+
return
220+
}
221+
c.HTML(http.StatusOK, "tags.html", map[string]any{
222+
"Labels": labels,
223+
"Navbars": config.Categories,
224+
"About": config.About,
225+
})
226+
}
227+
170228
func AboutPage(c *gin.Context) {
171229
discussion, err := api.FetchPost(config.About)
172230
if err != nil {
@@ -226,6 +284,8 @@ func main() {
226284
r.GET("/", cache.CacheByRequestURI(memoryCache, 30*time.Second), FetchPosts)
227285
r.GET("/category/:category_id/:category_name", cache.CacheByRequestURI(memoryCache, 30*time.Second), FetchPosts)
228286
r.GET("/post/:id/:title", cache.CacheByRequestURI(memoryCache, 1*time.Hour), FetchPost)
287+
r.GET("/tags", cache.CacheByRequestURI(memoryCache, 24*time.Hour), TagPage)
288+
r.GET("/search", cache.CacheByRequestURI(memoryCache, 24*time.Hour), SearchPosts)
229289
r.GET("/atom.xml", cache.CacheByRequestURI(memoryCache, 24*time.Hour), GenerateFeed)
230290
r.GET("/404", func(ctx *gin.Context) {
231291
ctx.HTML(http.StatusOK, "error.html", nil)

templates/base/navbar.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
<li><a class="relative block px-3 py-2 transition hover:text-teal-500 dark:hover:text-teal-400 item-menu"
1111
href="/category/{{.Id}}/{{.Name}}">{{.Name}}</a></li>
1212
{{ end }}
13-
13+
<li><a class="relative block px-3 py-2 transition hover:text-teal-500 dark:hover:text-teal-400 item-menu"
14+
href="/tags">Tags</a></li>
1415
{{ if .About }}
1516
<li><a class="relative block px-3 py-2 transition hover:text-teal-500 dark:hover:text-teal-400 item-menu"
1617
href="/about">About</a></li>

templates/pages/index.html

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
<meta charset="UTF-8">
88
<meta name="viewport" content="width=device-width, initial-scale=1.0">
99
<script src="https://cdn.tailwindcss.com"></script>
10+
<title>{{ .Title }}</title>
11+
<meta property="description" content="{{ .Title }}" />
12+
<meta property="title" content="{{ .Title }}" />
1013
</head>
1114

1215
<body class="flex h-full flex-col bg-zinc-50 dark:bg-black">
@@ -58,7 +61,7 @@ <h1 class="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm
5861
<div class="mx-auto max-w-2xl lg:max-w-5xl">
5962
<div class="mx-auto grid max-w-xl grid-cols-1 gap-y-20 lg:max-w-none lg:grid-cols-1">
6063
<div class="flex flex-col gap-8">
61-
{{range .Posts.Nodes}}
64+
{{range .Nodes}}
6265
{{ if .Category.Id | isExisted }}
6366
<article
6467
class="group relative flex flex-col items-start rounded-xl p-5 bg-white dark:bg-slate-800">
@@ -109,14 +112,14 @@ <h2 class="text-base font-semibold tracking-tight text-zinc-800 dark:text-zinc-1
109112

110113
<div class='flex items-center justify-center mt-10'>
111114
<div class="flex justify-center items-center space-x-4">
112-
{{ if .Posts.PageInfo.HasPreviousPage }}
115+
{{ if .PageInfo.HasPreviousPage }}
113116
<a class="px-2 py-1 text-3xl leading-6 text-slate-400 transition cursor-pointer "
114-
href="/?pre={{.Posts.PageInfo.StartCursor}}">
117+
href="/?pre={{.PageInfo.StartCursor}}">
115118
</a>
116119
{{ end }}
117-
{{ if .Posts.PageInfo.HasNextPage}}
120+
{{ if .PageInfo.HasNextPage}}
118121
<a class="px-2 py-1 text-3xl leading-6 text-slate-400 transition cursor-pointer"
119-
href="/?next={{.Posts.PageInfo.EndCursor}}"> > </a>
122+
href="/?next={{.PageInfo.EndCursor}}"> > </a>
120123
{{ end }}
121124
</div>
122125
</div>

templates/pages/tags.html

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!doctype html>
2+
<html class="h-full antialiased js-focus-visible"
3+
style="--header-position:sticky; --content-offset:116px; --header-height:180px; --header-mb:-116px; --header-top:0px; --avatar-top:0px; --avatar-image-transform:translate3d(0rem, 0, 0) scale(1); --avatar-border-transform:translate3d(-0.222222rem, 0, 0) scale(1.77778); --avatar-border-opacity:0;"
4+
data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">
5+
6+
<head>
7+
<meta charset="UTF-8">
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
9+
<title>Tags</title>
10+
<script src="https://cdn.tailwindcss.com"></script>
11+
<meta property="description" content="tags" />
12+
<meta property="title" content="Tags" />
13+
</head>
14+
15+
<body class="flex h-full flex-col bg-zinc-50 dark:bg-black justify-between">
16+
<div class="inset-0 flex justify-center sm:px-8">
17+
{{template "navbar.html" .}}
18+
</div>
19+
20+
<main>
21+
<div class="sm:px-8 mt-16">
22+
<div class="mx-auto max-w-7xl lg:px-8">
23+
<div class="relative px-4 sm:px-8 lg:px-12">
24+
<div class="mx-auto max-w-2xl lg:max-w-5xl">
25+
<div class="mx-auto grid max-w-xl grid-cols-1 gap-y-36 lg:max-w-none lg:grid-cols-1">
26+
<div class="flex flex-col gap-16">
27+
<div class="justify-content-start w-100 mb-4 text-start">
28+
{{ range .Labels }}
29+
<a class="ml-8 bg-green-200 my-2 inline-block rounded-full px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-teal-500 shadow-md transition duration-150 ease-in-out hover:bg-green-300 hover:shadow-lg focus:bg-green-300 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-green-400 active:shadow-lg dark:bg-green-600 dark:text-teal-200 dark:hover:bg-green-500 dark:focus:bg-green-500 dark:active:bg-green-400"
30+
href="/search?label={{.Name}}"> #{{.Name}} </a>
31+
{{ end }}
32+
</div>
33+
</div>
34+
</div>
35+
</div>
36+
</div>
37+
</div>
38+
</div>
39+
</main>
40+
41+
{{template "footer.html"}}
42+
</body>
43+
44+
</html>

0 commit comments

Comments
 (0)