From 3eb05da5649e0e6a86e2c13a8eb0e4c46c2fe460 Mon Sep 17 00:00:00 2001 From: thiloho <123883702+thiloho@users.noreply.github.com> Date: Wed, 14 Aug 2024 21:37:19 +0200 Subject: [PATCH] Use full text search instead of ilike for search functionality --- ...240810115846_image_upload_function.sql.sql | 2 +- .../20240814175120_full_text_search.sql | 86 +++++++++++++++++++ .../routes/(authenticated)/+page.server.ts | 2 +- .../[websiteId]/articles/+page.server.ts | 2 +- .../articles/[articleId]/+page.server.ts | 2 +- 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 rest-api/db/migrations/20240814175120_full_text_search.sql diff --git a/rest-api/db/migrations/20240810115846_image_upload_function.sql.sql b/rest-api/db/migrations/20240810115846_image_upload_function.sql.sql index e6220d3..e1e201d 100644 --- a/rest-api/db/migrations/20240810115846_image_upload_function.sql.sql +++ b/rest-api/db/migrations/20240810115846_image_upload_function.sql.sql @@ -11,7 +11,7 @@ DECLARE _allowed_mimetypes TEXT[] := ARRAY['image/png', 'image/svg+xml', 'image/jpeg', 'image/webp']; _max_file_size INT := 5 * 1024 * 1024; BEGIN - IF octet_length($1) = 0 THEN + IF OCTET_LENGTH($1) = 0 THEN RAISE invalid_parameter_value USING message = 'No file data was provided'; END IF; diff --git a/rest-api/db/migrations/20240814175120_full_text_search.sql b/rest-api/db/migrations/20240814175120_full_text_search.sql new file mode 100644 index 0000000..839533a --- /dev/null +++ b/rest-api/db/migrations/20240814175120_full_text_search.sql @@ -0,0 +1,86 @@ +-- migrate:up +ALTER TABLE internal.website + ADD COLUMN title_search TSVECTOR GENERATED ALWAYS AS (TO_TSVECTOR('english', title)) STORED; + +CREATE OR REPLACE VIEW api.website WITH ( security_invoker = ON +) AS +SELECT + id, + user_id, + content_type, + title, + created_at, + last_modified_at, + last_modified_by, + title_search -- New column +FROM + internal.website; + +GRANT SELECT, UPDATE, DELETE ON api.website TO authenticated_user; + +ALTER TABLE internal.article + ADD COLUMN title_description_search TSVECTOR GENERATED ALWAYS AS (TO_TSVECTOR('english', COALESCE(title, '') || ' ' || COALESCE(meta_description, ''))) STORED; + +CREATE OR REPLACE VIEW api.article WITH ( security_invoker = ON +) AS +SELECT + id, + website_id, + user_id, + title, + meta_description, + meta_author, + cover_image, + publication_date, + main_content, + created_at, + last_modified_at, + last_modified_by, + title_description_search -- New column +FROM + internal.article; + +GRANT SELECT, INSERT, UPDATE, DELETE ON api.article TO authenticated_user; + +-- migrate:down +DROP VIEW api.article; + +CREATE VIEW api.article WITH ( security_invoker = ON +) AS +SELECT + id, + website_id, + user_id, + title, + meta_description, + meta_author, + cover_image, + publication_date, + main_content, + created_at, + last_modified_at, + last_modified_by +FROM + internal.article; + +ALTER TABLE internal.article + DROP COLUMN title_description_search; + +DROP VIEW api.website; + +CREATE VIEW api.website WITH ( security_invoker = ON +) AS +SELECT + id, + user_id, + content_type, + title, + created_at, + last_modified_at, + last_modified_by +FROM + internal.website; + +ALTER TABLE internal.website + DROP COLUMN title_search; + diff --git a/web-app/src/routes/(authenticated)/+page.server.ts b/web-app/src/routes/(authenticated)/+page.server.ts index 7c6d456..d9a63ab 100644 --- a/web-app/src/routes/(authenticated)/+page.server.ts +++ b/web-app/src/routes/(authenticated)/+page.server.ts @@ -10,7 +10,7 @@ export const load: PageServerLoad = async ({ fetch, cookies, url }) => { const baseFetchUrl = `${API_BASE_PREFIX}/website`; if (searchQuery) { - params.append("title", `ilike.*${searchQuery}*`); + params.append("title_search", `wfts(english).${searchQuery}`); } switch (sortBy) { diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.server.ts index 892be91..da0bd19 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.server.ts @@ -10,7 +10,7 @@ export const load: PageServerLoad = async ({ params, fetch, cookies, url, parent const baseFetchUrl = `${API_BASE_PREFIX}/article?website_id=eq.${params.websiteId}&select=id,title`; if (searchQuery) { - parameters.append("title", `ilike.*${searchQuery}*`); + parameters.append("title_description_search", `wfts(english).${searchQuery}`); } switch (sortBy) { diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts index 49b6d57..6bb8fef 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.server.ts @@ -18,7 +18,7 @@ export const load: PageServerLoad = async ({ parent, params, cookies, fetch }) = }; export const actions: Actions = { - default: async ({ fetch, cookies, request, params, locals }) => { + default: async ({ fetch, cookies, request, params }) => { const data = await request.formData(); const coverFile = data.get("cover-image") as File;