Changing search to currently just search by like. Adding functions to get order by and direction based on search inputs.

This commit is contained in:
Bradley Shellnut 2024-04-27 19:52:57 -07:00
parent 45e9e5adb7
commit 0b58b3ff8f
9 changed files with 195 additions and 347 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { superForm, type Infer, type SuperValidated } from 'sveltekit-superforms'; import { superForm, type Infer, type SuperValidated } from 'sveltekit-superforms';
import { search_schema, type SearchSchema } from '$lib/zodValidation';
import * as Form from "$lib/components/ui/form";
import { zodClient } from 'sveltekit-superforms/adapters'; import { zodClient } from 'sveltekit-superforms/adapters';
import { search_schema, type SearchSchema } from '$lib/zodValidation';
import * as Form from "$lib/components/ui/form";
import Input from '$components/ui/input/input.svelte'; import Input from '$components/ui/input/input.svelte';
import Checkbox from '$components/ui/checkbox/checkbox.svelte'; import Checkbox from '$components/ui/checkbox/checkbox.svelte';

View file

@ -118,7 +118,7 @@ export async function createOrUpdateGameMinimal(locals: App.Locals, game: Games,
.insert(games) .insert(games)
.values({ .values({
name: game.name, name: game.name,
slug: kebabCase(game.name || game.slug || ''), slug: kebabCase(game.name ?? game.slug ?? ''),
description: game.description, description: game.description,
year_published: game.year_published, year_published: game.year_published,
url: externalUrl, url: externalUrl,

View file

@ -23,7 +23,7 @@ async function searchForGames(
method: 'GET', method: 'GET',
headers headers
}; };
const url = `/api/game/search${urlQueryParams ? `?${urlQueryParams}` : ''}`; const url = `/api/games/search${urlQueryParams ? `?${urlQueryParams}` : ''}`;
console.log('Calling internal api', url); console.log('Calling internal api', url);
const response = await eventFetch(url, requestInit); const response = await eventFetch(url, requestInit);
console.log('response from internal api', response); console.log('response from internal api', response);
@ -44,9 +44,9 @@ async function searchForGames(
!games.find((game: GameType) => game.slug === kebabCase(gameNameSearch)) !games.find((game: GameType) => game.slug === kebabCase(gameNameSearch))
) { ) {
console.log('No games found in DB for', gameNameSearch); console.log('No games found in DB for', gameNameSearch);
const searchQueryParams = urlQueryParams ? `?${urlQueryParams}` : '';
const externalResponse = await eventFetch( const externalResponse = await eventFetch(
`/api/external/search${urlQueryParams ? `?${urlQueryParams}` : ''}`, `/api/external/search${searchQueryParams}`,
requestInit requestInit
); );
@ -102,7 +102,7 @@ const defaults = {
exact: false, exact: false,
}; };
export const load: PageServerLoad = async ({ locals, fetch, url }) => { export const load = async ({ locals, fetch, url }) => {
const searchParams = Object.fromEntries(url?.searchParams); const searchParams = Object.fromEntries(url?.searchParams);
console.log('searchParams', searchParams); console.log('searchParams', searchParams);
searchParams.order = searchParams.order || defaults.order; searchParams.order = searchParams.order || defaults.order;

View file

@ -1,5 +1,5 @@
import { fail, error, type Actions } from '@sveltejs/kit'; import { fail, error, type Actions } from '@sveltejs/kit';
import { and, eq, ne } from 'drizzle-orm'; import { and, eq } from 'drizzle-orm';
import { Argon2id } from 'oslo/password'; import { Argon2id } from 'oslo/password';
import { decodeHex } from 'oslo/encoding'; import { decodeHex } from 'oslo/encoding';
import { TOTPController } from 'oslo/otp'; import { TOTPController } from 'oslo/otp';

View file

@ -1,7 +1,8 @@
import { error, json } from '@sveltejs/kit'; import { error, json } from '@sveltejs/kit';
import db from '$lib/drizzle.js'; import db from '$lib/drizzle.js';
import { eq, sql } from 'drizzle-orm'; import {asc, desc, eq, ilike, or } from 'drizzle-orm';
import { games } from '../../../../schema.js'; import { games } from '../../../../schema.js';
import kebabCase from "just-kebab-case";
// Search a user's collection // Search a user's collection
export const GET = async ({ url, locals }) => { export const GET = async ({ url, locals }) => {
@ -9,14 +10,14 @@ export const GET = async ({ url, locals }) => {
const q = searchParams?.q?.trim() || ''; const q = searchParams?.q?.trim() || '';
const limit = parseInt(searchParams?.limit) || 10; const limit = parseInt(searchParams?.limit) || 10;
const skip = parseInt(searchParams?.skip) || 0; const skip = parseInt(searchParams?.skip) || 0;
const order = searchParams?.order || 'desc'; const order: OrderDirection = searchParams?.order === 'desc' ? 'desc' : 'asc';
const exact = searchParams?.exact === 'true'; const exact = searchParams?.exact === 'true';
let orderBy = searchParams?.orderBy || 'slug'; let orderBy = searchParams?.orderBy || 'slug';
if (orderBy === 'name') { if (orderBy === 'name') {
orderBy = 'slug'; orderBy = 'slug';
} }
console.log(`q: ${q}, limit: ${limit}, skip: ${skip}, order: ${order}, exact: ${exact}`); console.log(`q: ${q}, limit: ${limit}, skip: ${skip}, order: ${order}, exact: ${exact}, orderBy: ${orderBy}`);
console.log(exact); console.log(exact);
if (exact) { if (exact) {
console.log('Exact Search API'); console.log('Exact Search API');
@ -44,9 +45,23 @@ export const GET = async ({ url, locals }) => {
slug: games.slug, slug: games.slug,
thumb_url: games.thumb_url thumb_url: games.thumb_url
}) })
.from(games) .from(games)
.where(sql`to_tsvector('simple', ${games.name}) || to_tsvector('simple', ${games.slug}) @@ to_tsquery('simple', ${q})`) .where(or(
.orderBy(sql`${orderBy} ${order}`).offset(skip).limit(limit) || []; ilike(games.name, `%${q}%`),
ilike(games.slug, `%${kebabCase(q)}%`)
))
.orderBy(getOrderDirection(order)(getOrderBy(orderBy)))
.offset(skip)
.limit(limit) || [];
// const foundGames = await db.select({
// id: games.id,
// name: games.name,
// slug: games.slug,
// thumb_url: games.thumb_url
// })
// .from(games)
// .where(sql`to_tsvector('simple', ${games.name}) || to_tsvector('simple', ${games.slug}) @@ to_tsquery('simple', ${q})`)
// .orderBy(sql`${orderBy} ${order}`).offset(skip).limit(limit) || [];
if (foundGames.length === 0) { if (foundGames.length === 0) {
error(404, { message: 'No games found' }); error(404, { message: 'No games found' });
} }
@ -54,3 +69,20 @@ export const GET = async ({ url, locals }) => {
return json(foundGames); return json(foundGames);
} }
}; };
type OrderDirection = 'asc' | 'desc';
const getOrderDirection = (direction: OrderDirection) => {
return direction === 'asc' ? asc: desc;
};
const getOrderBy = (orderBy: string) => {
switch (orderBy) {
case 'name':
return games.name;
case 'slug':
return games.slug;
default:
return games.slug;
}
}

View file

@ -1,12 +1,13 @@
import { customType } from 'drizzle-orm/pg-core'; import { customType } from 'drizzle-orm/pg-core';
import {sql} from "drizzle-orm";
function genExpWithWeights(input: string[]) { function genExpWithWeights(input: string[]) {
const columnExpressions = input.map((column, index) => { const columnExpressions = input.map((column, index) => {
const weight = String.fromCharCode(index + 65); const weight = String.fromCharCode(index + 65);
return `setweight(to_tsvector('english', coalesce(${column}, '')), '${weight}')`; return sql`setweight(to_tsvector('english', coalesce(${column}, '')), '${weight}')`;
}); });
return `tsvector GENERATED ALWAYS AS (${columnExpressions.join(' || ')}) STORED`; return sql`tsvector GENERATED ALWAYS AS (${columnExpressions.join(' || ')}) STORED`;
} }
export const tsvector = customType<{ export const tsvector = customType<{
@ -18,9 +19,9 @@ export const tsvector = customType<{
const sources = config.sources.join(" || ' ' || "); const sources = config.sources.join(" || ' ' || ");
return config.weighted return config.weighted
? genExpWithWeights(config.sources) ? genExpWithWeights(config.sources)
: `tsvector generated always as (to_tsvector('english', ${sources})) stored`; : sql`tsvector generated always as (to_tsvector('english', ${sources})) stored`;
} else { } else {
return `tsvector`; return sql`tsvector`;
} }
} }
}); });