API: Implement search resolver

This commit is contained in:
Drew DeVault 2022-07-09 15:48:03 +02:00
parent c1f917efb4
commit 8cf92fa220
4 changed files with 84 additions and 6 deletions

6
go.mod
View file

@ -3,20 +3,21 @@ module git.sr.ht/~sircmpwn/searchhut
go 1.18
require (
git.sr.ht/~sircmpwn/getopt v1.0.0
github.com/99designs/gqlgen v0.17.12
github.com/go-chi/chi v1.5.4
github.com/go-shiori/go-readability v0.0.0-20220215145315-dd6828d2f09b
github.com/lib/pq v1.10.6
github.com/temoto/robotstxt v1.1.2
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
github.com/vektah/gqlparser/v2 v2.4.6
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
)
require (
git.sr.ht/~sircmpwn/getopt v1.0.0 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/go-chi/chi v1.5.4 // indirect
github.com/go-shiori/dom v0.0.0-20210627111528-4e4722cd0d65 // indirect
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
@ -26,7 +27,6 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/urfave/cli/v2 v2.8.1 // indirect
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect

View file

@ -42,7 +42,7 @@ resolver:
# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
# - "git.sr.ht/~sircmpwn/searchhut/graph/model"
- "git.sr.ht/~sircmpwn/searchhut/graph/model"
# This section declares type mapping between the GraphQL and go type systems
#

18
graph/model/page.go Normal file
View file

@ -0,0 +1,18 @@
package model
import (
"time"
)
type Page struct {
ID int `json:"id"`
URL string `json:"url"`
LastIndexed time.Time `json:"last_indexed"`
Title *string `json:"title"`
Language *string `json:"language"`
Description *string `json:"description"`
Author *string `json:"author"`
Excerpt *string `json:"excerpt"`
DomainID int
}

View file

@ -5,18 +5,78 @@ package graph
import (
"context"
"database/sql"
"fmt"
"git.sr.ht/~sircmpwn/searchhut/database"
"git.sr.ht/~sircmpwn/searchhut/graph/generated"
"git.sr.ht/~sircmpwn/searchhut/graph/model"
)
// Search is the resolver for the search field.
func (r *queryResolver) Search(ctx context.Context, query string) ([]*model.Result, error) {
// Domain is the resolver for the domain field.
func (r *pageResolver) Domain(ctx context.Context, obj *model.Page) (*model.Domain, error) {
panic(fmt.Errorf("not implemented"))
}
// Search is the resolver for the search field.
func (r *queryResolver) Search(ctx context.Context, query string) ([]*model.Result, error) {
var results []*model.Result
if err := database.WithTx(ctx, &sql.TxOptions{
Isolation: 0,
ReadOnly: true,
}, func(tx *sql.Tx) error {
rows, err := tx.QueryContext(ctx, `
SELECT
id,
domain_id,
url,
last_index_date,
title,
language,
description,
author,
excerpt,
ts_rank_cd(fts_vector, websearch_to_tsquery('english', $1), 32) AS rank
FROM page
WHERE websearch_to_tsquery('english', $1) @@ fts_vector
ORDER BY rank DESC
LIMIT 10;
`, query)
if err != nil {
return err
}
for rows.Next() {
var (
rank float32
page model.Page
)
if err := rows.Scan(&page.ID, &page.DomainID, &page.URL,
&page.LastIndexed, &page.Title, &page.Language,
&page.Description, &page.Author, &page.Excerpt,
&rank); err != nil {
return err
}
results = append(results, &model.Result{
Page: &page,
Context: nil, // TODO
})
}
return nil
}); err != nil {
return nil, err
}
return results, nil
}
// Page returns generated.PageResolver implementation.
func (r *Resolver) Page() generated.PageResolver { return &pageResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type pageResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }