fixed up search a bit more, need to do pagination

pull/5/head
Adam Veldhousen 2023-05-20 00:39:30 -05:00
parent faec39add8
commit 87afb5e11b
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B
13 changed files with 112 additions and 42 deletions

View File

@ -0,0 +1,30 @@
# Auction site sources
| Name | API Available | Implemented |
|------------------|---------------|-------------|
| [Live Auctioneers](https://www.liveauctioneers.com/) | | JSON | :check: |
| [Catawiki](https://www.catawiki.com/en/) | | JSON | |
| [ProxiBid](https://www.proxibid.com/) | | None | |
| [The Saleroom](https://www.the-saleroom.com) | | None | |
- Catawiki
```
curl 'https://www.catawiki.com/buyer/api/v1/auctions?locale=en&per_page=25' \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0' \
-H 'Accept: application/json' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate, br' \
-H 'Referer: https://www.catawiki.com/en/' \
-H 'DNT: 1' \
-H 'Connection: keep-alive' \
-H 'Cookie: ab_first_visit=1; absmartly_id=a808d691-6a64-4797-bf77-97516c2917a9; cw_sid=2e67590dbaee388af8b5f1a59b4dd12f5d01759bc6a4603c6a8ae1add4637bdd; cw_ab=eaVcsgJ5TBW7bkaaWxN/6QAB; enable_marketing_cookies=true; enable_analytical_cookies=true; cookie_preferences_used_cta=accept_all; has_pending_cookie_consent_sync=true; rmb_disabled_at=1684553408868; _catawiki_session=KQn7aiPKK6FUaO8zr8yfwrAVUtfKv6wXQ9ntoW00Qux19aliGIcXXNUs6vmomxsOSruGU%2Bn%2BsNp8%2FzessvIc4ZZ7wkWJkhiHKHhV13dBUYm4Tu3SvP2nOPKXFzBJngnwgJRh%2BUIdoTv86KDmB%2B8%3D--aRi3oNveTClu9Grs--CFMYiz%2BOtTknj34sB2fblQ%3D%3D' \
-H 'Pragma: no-cache' \
-H 'Cache-Control: no-cache' \
-H 'TE: trailers'
```

View File

@ -20,7 +20,6 @@ service Catalog {
message AuctionSearchCriteria {
string searchTerm = 1;
int64 startBeforeTs = 2;
int32 page = 3;
int32 limit = 4;
}

View File

@ -22,10 +22,12 @@ CREATE TABLE IF NOT EXISTS catalog.upcoming_auctions_fts (
id SERIAL PRIMARY KEY,
auctionid INTEGER REFERENCES CATALOG.UPCOMING_AUCTIONS,
title VARCHAR(1024) NOT NULL,
sourcesitename VARCHAR(256) NOT NULL,
description TEXT NOT NULL,
ts tsvector GENERATED ALWAYS AS ((
setweight(to_tsvector('english', (sourcesitename)::TEXT), 'A') ||
setweight(to_tsvector('english', (title)::TEXT), 'A') ||
setweight(to_tsvector('english', (description)::TEXT), 'B')
setweight(to_tsvector('english', (description)::TEXT), 'C')
)) STORED
);
@ -82,7 +84,8 @@ BEGIN
auctionid,
title,
description
) VALUES ( auction_id, p_title, p_description );
sourcesitename
) VALUES ( auction_id, p_title, p_description, p_sourcesitename );
ELSE
-- 0 means there is a duplicate auction ID
auction_id = 0;

View File

@ -3,10 +3,10 @@ SELECT ua.*
FROM catalog.upcoming_auctions ua
LEFT JOIN catalog.upcoming_auctions_fts fts on ua.id = fts.auctionid
WHERE
ua.endts >= DATE(NOW()) AND
fts.ts @@ phraseto_tsquery(sqlc.arg(searchTerm))
ORDER BY ts_rank(fts.ts, phraseto_tsquery(sqlc.arg(searchTerm))) DESC
OFFSET sqlc.arg(page) * sqlc.arg(pageSize)
ua.endts >= DATE(NOW()) AND
fts.ts @@ websearch_to_tsquery(sqlc.arg(searchTerm))
ORDER BY ts_rank(fts.ts, websearch_to_tsquery(sqlc.arg(searchTerm))) DESC
OFFSET sqlc.arg(page)::INTEGER * sqlc.arg(pageSize)::INTEGER
LIMIT sqlc.arg(pageSize);
-- name: GetTotal :one

View File

@ -20,8 +20,8 @@ func (ps *PGCatalogStorage) GetUpcoming(ctx context.Context, q domain.UpcomingQu
if pgResults, err = ps.Queries.GetUpcoming(ctx, postgres.GetUpcomingParams{
Searchterm: q.Term,
Page: q.Page,
Pagesize: 64,
Page: int32(q.Page),
Pagesize: int32(q.Limit),
}); err != nil {
err = fmt.Errorf("could not get upcoming auctions from pg: %w", err)
return

View File

@ -24,6 +24,7 @@ type (
UpcomingQuery struct {
Term string
Page int
Limit int
StartDateFilter time.Time
}

View File

@ -3,7 +3,7 @@ package internal
import (
"context"
"fmt"
"time"
"math"
api "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api/grpc"
"git.vdhsn.com/barretthousen/barretthousen/src/catalog/internal/domain"
@ -28,26 +28,36 @@ type catalogHandler struct {
domain Domain
}
func (rh *catalogHandler) GetUpcoming(ctx context.Context, cmd *api.AuctionSearchCriteria) (r *api.GetUpcomingResult, err error) {
kernel.TraceLog.Printf("GetUpcoming[searchTerm: %q - page: %d - pageSize: %d]", cmd.GetSearchTerm(), cmd.GetPage(), cmd.GetLimit())
var results domain.UpcomingResults
if results, err = rh.domain.GetUpcoming(ctx, domain.UpcomingQuery{
Term: cmd.SearchTerm,
Page: int(cmd.GetPage()),
StartDateFilter: time.Unix(cmd.StartBeforeTs, 0),
func (rh *catalogHandler) GetUpcoming(ctx context.Context, cmd *api.AuctionSearchCriteria) (out *api.GetUpcomingResult, err error) {
page := int(math.Min(1, float64(cmd.GetPage())))
pageSize := int(cmd.GetLimit())
if pageSize < 32 {
pageSize = 32
} else if pageSize > 128 {
pageSize = 128
}
kernel.TraceLog.Printf("GetUpcoming[searchTerm: %q - page: %d - pageSize: %d]", cmd.GetSearchTerm(), page, pageSize)
var queryResp domain.UpcomingResults
if queryResp, err = rh.domain.GetUpcoming(ctx, domain.UpcomingQuery{
Term: cmd.SearchTerm,
Page: page,
Limit: pageSize,
}); err != nil {
err = fmt.Errorf("could not get upcoming items from domain: %w", err)
return
}
r = &api.GetUpcomingResult{
Page: int32(results.Page),
Total: int32(results.Total),
Results: make([]*api.Auction, len(results.Results)),
out = &api.GetUpcomingResult{
Page: int32(page),
Total: int32(queryResp.Total),
Found: int32(pageSize),
Results: make([]*api.Auction, len(queryResp.Results)),
}
for idx, a := range results.Results {
r.Results[idx] = &api.Auction{
for idx, a := range queryResp.Results {
out.Results[idx] = &api.Auction{
Id: int32(a.ID),
Fingerprint: a.Fingerprint,
Items: int32(a.ItemCount),

View File

@ -36,7 +36,7 @@ func (app *ProxyClientApp) Start(ctx context.Context) error {
Addr: fmt.Sprintf("0.0.0.0:%d", app.Port),
ReadHeaderTimeout: time.Second,
Handler: http.StripPrefix("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
kernel.TraceLog.Printf("{ \"Client\": \"%s\", \"Path\":\"%s\"} ", r.RemoteAddr, r.URL.Path)
kernel.TraceLog.Printf("{ \"Client\": \"%s\", \"Path\":\"%s\", \"User-Agent\":\"%s\" } ", r.RemoteAddr, r.URL, r.UserAgent())
w.Header().Set("Access-Control-Allow-Origin", "*")
grpcMux.ServeHTTP(w, r)
})),

View File

@ -1,16 +1,18 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
let query = '';
export let query = '';
export let page: Number = 0;
const dispatch = createEventDispatcher();
function execSearch() {
dispatch('search', {
query
query,
page
});
}
</script>
<form class="flex bg-bh-black rounded-md grow" on:submit|preventDefault={() => execSearch()}>
<input class="border-none grow py-1 px-2 mr-1 rounded-l-md" name="terms" bind:value={query} />
<button class="border-none rounded-r-md py-1 px-2" on:click={execSearch}>Submit</button>
<button class="border-none rounded-r-md py-1 px-2">Submit</button>
</form>

View File

@ -1,14 +1,30 @@
<script lang="ts">
import type { LayoutData } from './$types';
import { goto } from '$app/navigation';
import SearchBox from '$lib/SearchBox.svelte';
import '../app.css';
function onSubmit(evt: CustomEvent) {
export let data: LayoutData;
async function onSubmit(evt: CustomEvent) {
const { query, page } = evt.detail;
goto(query ? `/?query=${query}&page=${page || 0}` : '/');
await goto(query ? `/?query=${query}&page=${page || 0}` : '/', {
invalidateAll: true
});
}
</script>
<svelte:head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
Barretthousen: "{data.query}" results
</title>
<meta name="description" content="Search results for '{data.query}'" />
</svelte:head>
<main class="flex flex-col w-full">
<nav
class="flex w-full bg-bh-gold text-bh-black fixed top-0 left-0 right-0"
@ -23,11 +39,11 @@
<ul class="flex w-full py-3 items-center justify-between">
<li class="flex grow justify-center">
<span class="flex grow" style="max-width: 75%;">
<SearchBox on:search={onSubmit} />
<SearchBox on:search={onSubmit} page={data.page} />
</span>
</li>
<li class="px-6">
<span>I want email alerts!</span>
<!-- <span>I want email alerts!</span> -->
</li>
</ul>
</nav>

View File

@ -0,0 +1,10 @@
import type { LayoutLoad } from './$types';
export const load = (({ url }) => {
return {
query: url.searchParams.get('query') || '',
page: parseInt(url.searchParams.get('page') || '1'),
limit: parseInt(url.searchParams.get('pageSize') || '32'),
};
}) satisfies LayoutLoad;

View File

@ -4,18 +4,9 @@
export let data: PageData;
const hasResults = (data?.results || []).length > 0;
$: hasResults = (data?.results || []).length > 0;
</script>
{#if !hasResults}
<section class="flex w-full flex-col justify-center text-center">
<h1 class="text-2xl">No auctions found.</h1>
{#if data?.query !== 'watch'}
<p>Try searching <em><a href="/?query=watch">Watch</a></em></p>
{/if}
</section>
{/if}
{#if hasResults}
<h1 class="pb-5 text-lg">{data?.results.length} of {data?.total} Upcoming & Live Auctions</h1>
@ -26,4 +17,13 @@
</li>
{/each}
</ul>
{:else}
<section class="flex w-full flex-col justify-center text-center">
<h1 class="text-2xl">No auctions found.</h1>
{#if data?.query !== 'watch'}
<p>
Try searching <em><a href="/?query=watch">Watch</a></em>
</p>
{/if}
</section>
{/if}

View File

@ -5,7 +5,6 @@ const API_HOST = 'http://localhost:8000/api/v1'
export const load = (async ({ fetch, url }) => {
const searchTerm = url.searchParams.get('query') || '';
const currentPage = url.searchParams.get('page') || 1;
try {
const response = await fetch(API_HOST + `/upcoming?searchTerm=${searchTerm}&page=${currentPage}`);
const { page, total, results } = await response.json() || {};