Changeset 0.29.0 (#195)
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/notelinks"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
@@ -405,69 +404,30 @@ func (h *NoteHandler) Search(c *gin.Context) {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
// Use Postgres full-text search when available, LIKE fallback on SQLite
|
||||
if database.IsPostgres() {
|
||||
h.searchPostgres(c, userID, q, limit)
|
||||
return
|
||||
}
|
||||
|
||||
// SQLite: use store's LIKE-based search
|
||||
notes, _, err := h.stores.Notes.Search(c.Request.Context(), userID, q, store.ListOptions{Limit: limit})
|
||||
// SearchKeyword handles dialect internally: PG uses ts_rank/ts_headline, SQLite uses LIKE.
|
||||
storeResults, err := h.stores.Notes.SearchKeyword(c.Request.Context(), userID, q, limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "search failed"})
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]searchResult, 0, len(notes))
|
||||
for _, n := range notes {
|
||||
results = append(results, searchResult{
|
||||
noteListItem: toNoteListItem(n),
|
||||
Rank: 1.0,
|
||||
Headline: "",
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": results,
|
||||
"query": q,
|
||||
"total": len(results),
|
||||
})
|
||||
}
|
||||
|
||||
// searchPostgres uses Postgres full-text search with ts_rank and ts_headline.
|
||||
func (h *NoteHandler) searchPostgres(c *gin.Context, userID, q string, limit int) {
|
||||
rows, err := database.DB.Query(`
|
||||
SELECT id, title, folder_path, tags, LEFT(content, 200),
|
||||
created_at::text, updated_at::text,
|
||||
ts_rank(search_vector, plainto_tsquery('english', $2)) AS rank,
|
||||
ts_headline('english', content, plainto_tsquery('english', $2),
|
||||
'MaxWords=40, MinWords=20, StartSel=**, StopSel=**') AS headline
|
||||
FROM notes
|
||||
WHERE user_id = $1
|
||||
AND search_vector @@ plainto_tsquery('english', $2)
|
||||
ORDER BY rank DESC
|
||||
LIMIT $3
|
||||
`, userID, q, limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "search failed"})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// Import pq at call site to avoid pulling it in for SQLite builds
|
||||
results := make([]searchResult, 0)
|
||||
for rows.Next() {
|
||||
var r searchResult
|
||||
var tags []string
|
||||
if err := rows.Scan(&r.ID, &r.Title, &r.FolderPath, pgScanStringArray(&tags), &r.Preview,
|
||||
&r.CreatedAt, &r.UpdatedAt, &r.Rank, &r.Headline); err != nil {
|
||||
continue
|
||||
}
|
||||
results := make([]searchResult, 0, len(storeResults))
|
||||
for _, sr := range storeResults {
|
||||
tags := sr.Tags
|
||||
if tags == nil {
|
||||
tags = []string{}
|
||||
}
|
||||
r.Tags = tags
|
||||
results = append(results, r)
|
||||
results = append(results, searchResult{
|
||||
noteListItem: noteListItem{
|
||||
ID: sr.ID,
|
||||
Title: sr.Title,
|
||||
FolderPath: sr.FolderPath,
|
||||
Tags: tags,
|
||||
Preview: sr.Excerpt,
|
||||
},
|
||||
Rank: sr.Rank,
|
||||
Headline: sr.Headline,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -483,29 +443,19 @@ func (h *NoteHandler) searchPostgres(c *gin.Context, userID, q string, limit int
|
||||
func (h *NoteHandler) ListFolders(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
|
||||
rows, err := database.DB.Query(database.Q(`
|
||||
SELECT DISTINCT folder_path, COUNT(*) AS count
|
||||
FROM notes WHERE user_id = $1
|
||||
GROUP BY folder_path
|
||||
ORDER BY folder_path
|
||||
`), userID)
|
||||
storeFolders, err := h.stores.Notes.ListFolders(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list folders"})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type folderInfo struct {
|
||||
Path string `json:"path"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
folders := make([]folderInfo, 0)
|
||||
for rows.Next() {
|
||||
var f folderInfo
|
||||
if err := rows.Scan(&f.Path, &f.Count); err != nil {
|
||||
continue
|
||||
}
|
||||
folders = append(folders, f)
|
||||
folders := make([]folderInfo, 0, len(storeFolders))
|
||||
for _, f := range storeFolders {
|
||||
folders = append(folders, folderInfo{Path: f.Path, Count: f.Count})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"folders": folders})
|
||||
|
||||
Reference in New Issue
Block a user