fixed up search a bit more, need to do pagination
parent
faec39add8
commit
87afb5e11b
|
|
@ -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'
|
||||
```
|
||||
|
|
@ -20,7 +20,6 @@ service Catalog {
|
|||
|
||||
message AuctionSearchCriteria {
|
||||
string searchTerm = 1;
|
||||
int64 startBeforeTs = 2;
|
||||
int32 page = 3;
|
||||
int32 limit = 4;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type (
|
|||
UpcomingQuery struct {
|
||||
Term string
|
||||
Page int
|
||||
Limit int
|
||||
StartDateFilter time.Time
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})),
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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() || {};
|
||||
|
|
|
|||
Loading…
Reference in New Issue