FEAT: Implemented working search bar using FTS and Ranking

This commit is contained in:
white
2025-02-11 16:38:46 +00:00
parent 905e879c60
commit 1a572cc172
5 changed files with 53 additions and 26 deletions

View File

@@ -18,17 +18,28 @@ const SearchBar: React.FC = () => {
// Perform search when debounced query changes // Perform search when debounced query changes
useEffect(() => { useEffect(() => {
if (debouncedQuery.trim()) { if (debouncedQuery.trim()) {
fetch(`/api/search/${debouncedQuery}`) const fetchSearchResults = async () => {
.then((response) => response.json()) try {
.then((data) => { const response = await fetch("/api/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ query: debouncedQuery }), // <-- Fixed payload
});
const data = await response.json();
console.log("Search results:", data); console.log("Search results:", data);
// Handle the search results here // Handle the search results here
}) } catch (error) {
.catch((error) => {
console.error("Error performing search:", error); console.error("Error performing search:", error);
}); }
};
fetchSearchResults(); // Call the async function
} }
}, [debouncedQuery]); }, [debouncedQuery]);
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value); setSearchQuery(e.target.value);

View File

@@ -12,7 +12,8 @@ from blueprints.chat import chat_bp
from blueprints.oauth import oauth_bp, init_oauth from blueprints.oauth import oauth_bp, init_oauth
from blueprints.socket import socketio from blueprints.socket import socketio
from celery import Celery from celery import Celery
from celery_tasks import celery_init_app from celery_tasks import celery_init_app#
from blueprints.search_bar import search_bp
from os import getenv from os import getenv
@@ -69,6 +70,7 @@ def create_app():
app.register_blueprint(stream_bp) app.register_blueprint(stream_bp)
app.register_blueprint(chat_bp) app.register_blueprint(chat_bp)
app.register_blueprint(oauth_bp) app.register_blueprint(oauth_bp)
app.register_blueprint(search_bp)
socketio.init_app(app) socketio.init_app(app)

View File

@@ -1,15 +1,19 @@
from flask import Blueprint, jsonify from flask import Blueprint, jsonify, request
from database.database import Database from database.database import Database
from utils.utils import sanitize
search_bp = Blueprint("search", __name__) search_bp = Blueprint("search", __name__)
@search_bp.route("/search/<string:query>", methods=["GET", "POST"]) @search_bp.route("/search", methods=["POST"])
def search_results(query: str): def search_results():
""" """
Return the most similar search results Return the most similar search results
This is the main route that displays a subsection of each search topic This is the main route that displays a subsection of each search topic
""" """
data = request.get_json()
query = sanitize(data["query"])
# Create the connection to the database # Create the connection to the database
db = Database() db = Database()
db.create_connection() db.create_connection()
@@ -17,32 +21,37 @@ def search_results(query: str):
# Get the most accurate search results # Get the most accurate search results
# 3 categories # 3 categories
categories = db.fetchall(""" categories = db.fetchall("""
SELECT bm25(category_fts), rank, f.category_id, f.category_name SELECT bm25(category_fts) AS score, c.category_id, c.category_name
FROM categories AS c FROM categories AS c
INNER JOIN category_fts AS f ON c.category_id = f.category_id INNER JOIN category_fts AS f ON c.category_id = f.category_id
WHERE category_fts MATCH ? WHERE f.category_name LIKE '%' || ? || '%'
LIMIT 3; ORDER BY score ASC
LIMIT 3;
""", (query,)) """, (query,))
# 3 users # 3 users
users = db.fetchall(""" users = db.fetchall("""
SELECT bm25(user_fts), rank, f.user_id, f.username, f.is_live SELECT bm25(user_fts) AS score, u.user_id, u.username, u.is_live
FROM users u FROM users AS u
INNER JOIN user_fts f ON u.user_id = f.user_id INNER JOIN user_fts AS f ON u.user_id = f.user_id
WHERE user_fts MATCH ? WHERE f.username LIKE '%' || ? || '%'
LIMIT 3; ORDER BY score ASC
LIMIT 3;
""", (query,)) """, (query,))
# 3 streams # 3 streams
streams = db.fetchall(""" streams = db.fetchall("""
SELECT bm25(stream_fts), rank, f.user_id, f.title, f.num_viewers, f.category_id SELECT bm25(stream_fts) AS score, s.user_id, s.title, s.num_viewers, s.category_id
FROM streams s FROM streams AS s
INNER JOIN stream_fts f ON s.user_id = f.user_id INNER JOIN stream_fts AS f ON s.user_id = f.user_id
WHERE stream_fts MATCH ? WHERE f.title LIKE '%' || ? || '%'
LIMIT 3; ORDER BY score ASC
LIMIT 3;
""", (query,)) """, (query,))
db.close_connection() db.close_connection()
print(query, streams, users, categories, flush=True)
return jsonify({"categories": categories, "users": users, "streams": streams}) return jsonify({"categories": categories, "users": users, "streams": streams})

Binary file not shown.

View File

@@ -37,7 +37,7 @@ def get_most_popular_category() -> Optional[List[dict]]:
return category return category
def sanitize(user_input: str, input_type="username") -> str: def sanitize(user_input: str, input_type="default") -> str:
""" """
Sanitizes user input based on the specified input type. Sanitizes user input based on the specified input type.
@@ -63,6 +63,11 @@ def sanitize(user_input: str, input_type="username") -> str:
"min_length": 8, "min_length": 8,
"max_length": 256, "max_length": 256,
}, },
"default": {
"pattern": r"^[\S]+$", # Non-whitespace characters only
"min_length": 1,
"max_length": 50,
},
} }
# Get the validation rules for the specified type # Get the validation rules for the specified type