Skip to content

Commit d86d93e

Browse files
committed
✨ feat: add full text search endpoint close #9
1 parent 34921d3 commit d86d93e

File tree

8 files changed

+109
-3
lines changed

8 files changed

+109
-3
lines changed

api/handlers.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type AddBookRequest struct {
1717
Isbn string `json:"isbn"`
1818
}
1919

20+
// POST /books
2021
func (h *BookHandler) AddBook(c echo.Context) error {
2122
ctx := c.Request().Context()
2223
var req AddBookRequest
@@ -30,14 +31,15 @@ func (h *BookHandler) AddBook(c echo.Context) error {
3031
default:
3132
}
3233

33-
err := h.Service.AddBook(c.Request().Context(), req.Isbn, req.Title, req.Description)
34+
err := h.Service.AddBook(ctx, req.Isbn, req.Title, req.Description)
3435
if err != nil {
3536
return c.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
3637
}
3738

3839
return c.JSON(http.StatusCreated, echo.Map{"status": "book added"})
3940
}
4041

42+
// GET /search/vector?q=
4143
func (h *BookHandler) SearchBooks(c echo.Context) error {
4244
ctx := c.Request().Context()
4345
query := c.QueryParam("q")
@@ -61,3 +63,28 @@ func (h *BookHandler) SearchBooks(c echo.Context) error {
6163

6264
return c.JSON(http.StatusOK, results)
6365
}
66+
67+
// GET /search/text?q=
68+
func (h *BookHandler) FullTextSearch(c echo.Context) error {
69+
ctx := c.Request().Context()
70+
query := c.QueryParam("q")
71+
if query == "" {
72+
return c.JSON(http.StatusBadRequest, echo.Map{"error": "missing query"})
73+
}
74+
75+
select {
76+
case <-ctx.Done():
77+
return c.JSON(http.StatusRequestTimeout, echo.Map{"error": "request canceled or timed out"})
78+
default:
79+
}
80+
81+
results, err := h.Service.FullTextSearch(ctx, query)
82+
if err != nil {
83+
if ctx.Err() != nil {
84+
return c.JSON(http.StatusRequestTimeout, echo.Map{"error": "request timed out"})
85+
}
86+
return c.JSON(http.StatusInternalServerError, echo.Map{"error": err.Error()})
87+
}
88+
89+
return c.JSON(http.StatusOK, results)
90+
}

cmd/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ func main() {
9898
e.GET("/ping", func(c echo.Context) error {
9999
return c.String(200, "pong")
100100
})
101-
e.GET("/search", bookHandler.SearchBooks)
101+
e.GET("/search/semantic", bookHandler.SearchBooks)
102+
e.GET("/search/text", bookHandler.FullTextSearch)
102103
e.POST("/books", bookHandler.AddBook)
103104

104105
// e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.port)))

db/books.sql

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,11 @@ LIMIT 5;
1111
-- name: GetBookByISBN :one
1212
SELECT id, isbn
1313
FROM books
14-
WHERE isbn = $1;
14+
WHERE isbn = $1;
15+
16+
-- name: SearchBooksByText :many
17+
SELECT id, isbn, title, description
18+
FROM books
19+
WHERE tsv @@ plainto_tsquery('english', $1)
20+
ORDER BY ts_rank(tsv, plainto_tsquery('english', $1)) DESC
21+
LIMIT 10;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DROP INDEX IF EXISTS idx_books_tsv;
2+
3+
ALTER TABLE books
4+
DROP COLUMN IF EXISTS tsv;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ALTER TABLE books
2+
ADD COLUMN tsv tsvector GENERATED ALWAYS AS (
3+
to_tsvector('english', coalesce(title, '') || ' ' || coalesce(description, ''))
4+
) STORED;
5+
6+
CREATE INDEX idx_books_tsv ON books USING GIN (tsv);

internal/repository/books.sql.go

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

internal/repository/models.go

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

internal/service/book_service.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,23 @@ func cosineSimilarity(a, b []float32) (float64, error) {
124124

125125
return dot / (math.Sqrt(normA) * math.Sqrt(normB)), nil
126126
}
127+
128+
func (s *BookService) FullTextSearch(ctx context.Context, query string) ([]repository.SearchBooksByTextRow, error) {
129+
select {
130+
case <-ctx.Done():
131+
return nil, ctx.Err()
132+
default:
133+
}
134+
135+
if query == "" {
136+
return nil, fmt.Errorf("search query cannot be empty")
137+
}
138+
139+
books, err := s.Repository.SearchBooksByText(ctx, query)
140+
if err != nil {
141+
s.Logger.Error("Full-text search failed", "query", query, "error", err)
142+
return nil, fmt.Errorf("full-text search failed: %w", err)
143+
}
144+
145+
return books, nil
146+
}

0 commit comments

Comments
 (0)