FEAT: Implemented working search bar using FTS and Ranking
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user