top of page

GoGool Search Engine Project 

Screenshot 2025-05-27 205857.png

This is just a screenshot. Click the button in the bottom of the page for actual demo

Overview

The project is a web-based search engine called GoCool. It allows users to:
Sign up, log in, and log out (with authentication via Supabase).
Search for content across multiple categories: Web, Images, Videos, and News.
View their search history (if logged in).

Frontend (static/app.js & templates/index.html)
Key Features:
Authentication Modal: Users can log in or sign up via a modal form. Auth state is managed in the UI.
Search Interface: Users can enter a query and search across all categories with one click or by pressing Enter.
Results Display: Results for Web, Images, Videos, and News are shown in separate sections.
Search History: Logged-in users can view their past searches in a modal.
Responsive UI: Uses CSS for a clean, modern look with modals and grid layouts for results.

Backend (main.py)
Framework: Flask
Key Endpoints:
/ — Renders the main search page.
/signup — Registers a new user via Supabase.
/login — Authenticates a user and stores their info in the session.
/logout — Logs out the user (clears session).
/search — Handles search requests for:
Images: Uses Pexels API.
News: Uses NewsAPI.
Videos: Uses YouTube Data API.
History: Saves search queries to Supabase if user is logged in.
/websearch — Uses SerpAPI to fetch Google search results.
/search-history — Returns the logged-in user's search history from Supabase.
/video-search — (Appears redundant) Fetches YouTube videos for a query.
Authentication & Session:
Uses Flask sessions to track logged-in users.
Supabase is used for user management and storing search history.

APIs Used:
Supabase: User authentication and search history storage.
Pexels: Image search.
NewsAPI: News articles.
YouTube Data API: Video search.
SerpAPI: Web search (Google).

 

​​​​​​​​​​​Programming Languages / Libraries / Frameworks / Databases Used

 

HTML, Python, Flask

How does it work (Workflow)?

Data Flow
User visits site: Main page loads, checks auth status.
User logs in/signs up: Auth modal appears, credentials sent to backend, session updated.
User searches: Query sent to backend endpoints, which call external APIs and return results.
Results displayed: JS updates the DOM with results for each category.
Search history: If logged in, each search is saved; user can view history in a modal.

The role of 'main.py'

It does a role of web server and routing. It uses Flask to run a web server and defined all the HTTP request (endpoints) frontend interacts with such as /, /login, /signup, /search, /websearch, /search-history, and /logout. 

from flask import Flask, render_template, request, jsonify, session, redirect
import requests
from supabase import create_client, Client
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'your-secret-key-replace-this'  # Replace with a secure secret key

The secret key is a crucial factor used by Flask to securely sign the session cookies and other security-needs. Session security is used by Flask to encrypt and sign the data stored in the user's session cookie. With this, the users cannot tamper with their session data. 

​import os Imports os module which allows the code to interact with environmental variable and operating system 
supabase_url = os.getenv('SUPABASE_URL') Tries to get the value of the environment variable SUPABASE_URL.
supabase_key = os.getenv('SUPABASE_ANON_KEY') Tries to get the value of the environment variable SUPABASE_ANON_KEY.


if not supabase_url or not supabase_key:
    raise ValueError("Missing Supabase credentials. Please set SUPABASE_URL and SUPABASE_ANON_KEY in Secrets.")

These lines: check if either variable is missing or empty. It does not check if the credentials are correct or valid.
It only checks if they are present (i.e., not empty).

​supabase: Client = create_client(supabase_url, supabase_key)  -> creating a Supabase client using the supabase_url and supabase_key variables.

Sign-up, Login, and Logout 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

@app.route('/')
def home():
    return render_template('index.html', user=session.get('user'))

What these lines do are:

route (handles request to the root URL of the web page (original homepage web address)​

function (when the root URL gets visited, it calls the home() function)

Template rendering (renders the index.html) template

User context (passes a variable named 'user' to the template. The value of user is taken from the session. If no user in the session, 'user' will be 'None')

It displays the homepage (index.html).
If a user is logged in (i.e., their info is stored in the session), their info is passed to the template.
If no user is logged in, user will be None in the template.

*What is 'session'?

In a Flask application, session is a special object provided by Flask to store information about a user's session (data that persists between requests for a specific user, like login state).

@app.route('/signup', methods=['POST'])
def signup():
    data = request.json
    try:
        response = supabase.auth.sign_up({
            "email": data['email'],
            "password": data['password']
        })
        return jsonify({"success": True, "message": "Please check your email for confirmation link"})
    except Exception as e:
        return jsonify({"error": str(e)}), 400

@app.route('/signup', methods=['POST'])->This defines a route in your Flask app that listens for POST requests at the /signup URL.

def signup(): ->This defines the function that will be called when a POST request is made to /signup.

data = request.json-> This gets the JSON data sent in the request body (e.g., {"email": "...", "password": "..."}) and stores it in the variable data.

     response = supabase.auth.sign_up({
            "email": data['email'],
            "password": data['password']
        })

These lines calls the Supabase authentification API to create a new user with provided email and password

data['email'] and data['password'] are taken from the JSON sent by the client.

return jsonify({"success": True, "message": "Please check your email for confirmation link"})->If the signup is successful, this returns a JSON response indicating success and tells the user to check their email for a confirmation link.

except Exception as e:
     return jsonify({"error": str(e)}), 400

These lines are for when any errors happen like email is already registered or the password is too weak. This catches the exception and returns a JSON response with the error message also 400 status code (bad request).

​@app.route('/login', methods=['POST'])-> sets up a route that listens for POST requests at the /login URL.

def login(): This defines the function to handle those requests.

data = request.json

This retrieves the JSON data sent in the request body (should include "email" and "password"), and stores it in the variable data.

      response = supabase.auth.sign_in_with_password({
            "email": data['email'],
            "password": data['password']
        })

This block is for calling Supabase's authentication API to try to sign in with the provided email and password.

       session['user'] = {
            'email': response.user.email,
            'id': response.user.id
        }

If login is successful, this stores the email and ID in the Flask session. This is for users to stay logged in for future requests.

        return jsonify({
            "success": True,
            "user": {
                "email": response.user.email,
                "id": response.user.id
            }
        })

This returns a JSON response. 

    except Exception as e:
        return jsonify({"error": str(e)}), 400

Same as in the previous snippet, error message and bad request (400) indication. 

@app.route('/logout')-> Sets up a route a t /logout
def logout(): This one clears all data from the session
    session.clear()
    return redirect('/')

This is for logout. Session clear function and redirects to the home. 

Those sign up, login and logout features are being corresponded by the snippets (templates/index.html) below. 

Login and Signup Frontend Triggers 

<div id="auth-container">
        <div id="logged-out">
            <button onclick="showLoginForm()" style="background-color: #ff8c00;">Login</button>
            <button onclick="showSignupForm()">Signup</button>

        </div>
        <div id="logged-in" style="display: none;">
            <span style="margin-right: 10px;">Welcome, <span id="user-email"></span>!</span>
            <button onclick="logout()" style="background-color: #dc3545;">Logout</button>
            <button onclick="showSearchHistory()">History</button>
        </div>
    </div>

Login Button:
<button onclick="showLoginForm()">Login</button>
When clicked, this shows the login form modal.
Signup Button:
<button onclick="showSignupForm()">Signup</button>
When clicked, this shows the signup form modal.

​Logged-in Section:
<div id="logged-in" style="display: none;"> ... </div>
This is shown when the user is logged in (after successful login/signup).

Auth Modal Login/Signup Form (Pop up window)

<div id="auth-modal" class="modal-content">
   <h2 id="auth-title">Login</h2>
   <input type="email" id="auth-email" placeholder="Email">
   <input type="password" id="auth-password" placeholder="Password">

   <div>
       <button onclick="submitAuth()">Submit</button>
       <button onclick="closeAuthModal()">Close</button>

   </div>
</div>

This modal pops up ​(a popup window) for both login and signup (the title and behavior can be switched by JavaScript).
When the user submits, JavaScript will send a POST request to /login or /signup (your Flask routes).

 

<div id="auth-modal" class="modal-content">
This is the container for the modal. It’s hidden by default and shown when the user wants to log in or sign up.

 

<h2 id="auth-title">Login</h2>
The title of the modal. It can be changed dynamically (by JavaScript) to "Login" or "Signup" depending on the action.

 

<input type="email" id="auth-email" placeholder="Email">
An input field for the user to enter their email address.

 

<input type="password" id="auth-password" placeholder="Password">
An input field for the user to enter their password.

 

<button onclick="submitAuth()">Submit</button>
When clicked, this calls a JavaScript function (submitAuth()) that collects the email and password and sends them to the backend (Flask) for login or signup.

 

<button onclick="closeAuthModal()">Close</button>
When clicked, this closes the modal dialog.

 

*What is Flask? 
Flask is a lightweight web framework for building web applications in Python. It allows you to create web servers, handle HTTP requests, and render web pages or APIs. Flask is known for being simple, flexible, and easy to get started with.  

Flask is a Python library—you install it with pip install flask. You write your web application code in Python, using Flask’s classes and functions. Flask uses Python’s syntax and features, so if you know Python, you can use Flask.

Actual search part

From templates / index.html

<div class="search-container">
        <h1>GoCool</h1>
        <div class="search-box">
            <input type="text" id="searchInput" placeholder="Search anything..." value="Drone">
            <button onclick="performSearch()">Search</button>
        </div>
    </div>

    <a href="/" class="home-button">GoCool</a>
    <div class="search-results" id="searchResults">
        <div class="section">
            <h2>Web Results</h2>
            <div id="web-results"></div>
        </div>
        
        <div class="section">
            <h2>Images</h2>
            <div id="image-results" class="results-grid"></div>
        </div>
        
        <div class="section">
            <h2>Videos</h2>
            <div id="video-results" class="video-container"></div>
        </div>
        
        <div class="section">
            <h2>News</h2>
            <div id="news-results"></div>
        </div>
    </div>c

 

Input box (id="searchInput") is where user types in their search query. 

Search button (button onclick) calls the JavaScript function performSearch() which sends the search query to the backend and get the result. Results sections: These empty <div>s (id="web-results", id="image-results", etc.) are where the search results will be displayed.

Search feature in main.py

@app.route('/search', methods=['POST'])
def search():
    data = request.json
    query = data.get('query', 'drone')
    category = data.get('category')

    # Save search history if user is logged in
    try:
        if 'user' in session:
            supabase.table('search_history').insert({
                'user_id': session['user']['id'],
                'query': query,
                'category': category,
                'timestamp': datetime.utcnow().isoformat()
            }).execute()
    except Exception as e:
        print("Error saving search history:", str(e))

    if category == 'image':
        pexels_url = 'https://api.pexels.com/v1/search'
        headers = {'Authorization': ' '}
        params = {'query': query, 'per_page': 10}
        response = requests.get(pexels_url, headers=headers, params=params)

        if response.status_code == 200:
            results = response.json()
            images = []
            for photo in results.get('photos', []):
                if photo and 'src' in photo:
                    images.append({
                        'title': photo.get('alt', 'Image'),
                        'url': photo['src'].get('medium', '')
                    })
        else:
            images = []
        return jsonify({'image': images})

    elif category == 'news':
        url = f"https://newsapi.org/v2/everything?q={query}&sortBy=publishedAt&language=en&apiKey=a55de15e171c43838e007ea6600495a8"
        response = requests.get(url)
        news_data = response.json()

        articles = [{
            'title': article['title'],
            'url': article['url'],
            'description': article.get('description'),
            'image': article.get('urlToImage')
        } for article in news_data.get('articles', [])[:6]]

        return jsonify({'news': articles})
    
    elif category == 'video':
        api_key = os.getenv('YOUTUBE_API_KEY', '').strip("'")
        if not api_key:
            return jsonify({'video': [], 'error': 'YouTube API key not configured'})
            
        url = "https://www.googleapis.com/youtube/v3/search"
        params = {
            'part': 'snippet',
            'type': 'video',
            'q': query,
            'maxResults': 5,
            'key': api_key
        }
        
        try:
            response = requests.get(url, params=params)
            response.raise_for_status()
            data = response.json()
            
            videos = [{
                'title': item['snippet']['title'],
                'url': f"https://www.youtube.com/embed/{item['id']['videoId']}"
            } for item in data.get('items', [])]
            
            return jsonify({'video': videos})
        except Exception as e:
            print("YouTube API error:", str(e))
            return jsonify({'video': [], 'error': 'Failed to fetch videos'})

    else:
        return jsonify({'error': 'Unsupported category'})

@app.route('/websearch', methods=['POST'])
def websearch():
    query = request.json.get('query')
    url = f"https://serpapi.com/search.json"
    params = {
        "q": query,
        "engine": "google",
        "api_key": ' ',
        "num": 20
    }
    response = requests.get(url, params=params)
    data = response.json()

    results = []
    for item in data.get('organic_results', []):
        results.append({
            'title': item.get('title'),
            'url': item.get('link')
        })

    return jsonify({'web': results})

@app.route('/search-history')
def get_search_history():
    if 'user' not in session:
        return jsonify({"error": "Not authenticated"}), 401

@app.route('/video-search', methods=['POST'])
def video_search():
    query = request.json.get('query', 'drone')
    api_key = os.getenv('YOUTUBE_API_KEY', '').strip("'")  # Remove any quotes
    
    if not api_key:
        return jsonify({"error": "YouTube API key not configured"}), 500
        
    url = f"https://www.googleapis.com/youtube/v3/search"
    params = {
        'part': 'snippet',
        'type': 'video',
        'q': query,
        'maxResults': 5,
        'key': api_key
    }
    
    try:
        response = requests.get(url, params=params)
        response.raise_for_status()
        return jsonify(response.json())
    except Exception as e:
        print("YouTube API error:", str(e))
        return jsonify({"error": "Failed to fetch videos"}), 500

    response = supabase.table('search_history').select('*').eq(
        'user_id', session['user']['id']
    ).order('timestamp', desc=True).execute()

    return jsonify({"history": response.data})
 

There are four @app.route in the actual search feature. ​

/search (POST)
Handles: Image, news, and video searches (based on the category sent in the request).
How it works:
Receives a JSON payload with query and category.
If the user is logged in, saves the search to the search_history table in Supabase.
Depending on category:
image: Calls the Pexels API for images.
news: Calls the NewsAPI for news articles.
video: Calls the YouTube API for videos.
Returns the results as JSON for the requested category.
If the category is unsupported, returns an error.

 

/websearch (POST)
Handles: Web (Google) search results.
How it works:
Receives a JSON payload with query.
Calls the SerpAPI (Google search API) with the query.
Extracts the organic search results (title and URL).
Returns the results as JSON.

/search-history (GET)
Handles: Retrieving the logged-in user's search history.
How it works:
Checks if the user is logged in (exists in session).
If not logged in, returns an authentication error.
(Note: The actual code to fetch and return the history is missing in your snippet, but the intent is to return the user's search history from Supabase.)

/video-search (POST)
Handles: Video search (YouTube).
How it works:
Receives a JSON payload with query.
Uses the YouTube API to search for videos matching the query.
Returns the raw YouTube API response as JSON.
If the API key is missing or there’s an error, returns an error message.
(Note: There is some unrea
chable code after the try/except block that attempts to fetch search history; this should be moved to the /search-history route.)

*GET vs POST in coding language

Use GET to get data.
Use POST to post (send) data to the server.

Walkthrough of app.route('/search')

@app.route('/search', methods=['POST'])
def search():
   data = request.json
   query = data.get('query', 'drone')
   category = data.get('category')

    # Save search history if user is logged in

 

Flask to listen for POST request at the /search URL. When a POST request is made to /search, it calls the search() function.

def search() is the function that handles the request. Then, it gets the data (JSON format) in the body of the POST request.

In here, it gets the value for the key 'query' from data dictionary ('query, 'drone'). 'query' will be the search term user enters (in this case, 'drone' is the query value). Next, the snippet will get the value for the key 'category' from 'data' dictionary. 'category' will be the one user selects ('images', 'news', 'video')


   try:
       if 'user' in session:
           supabase.table('search_history').insert({
               'user_id': session['user']['id'],
               'query': query,
               'category': category,
               'timestamp': datetime.utcnow().isoformat()
           }).execute()
   except Exception as e:
       print("Error saving search history:", str(e))


Checks if a user is currently logged in (i.e., their info is stored in the session).

If a user is logged in, this line saves the search to the search_history table in your Supabase database. 

The last lines are for when error occurs (invalid data or database down) and print error message.

    if category == 'image':
       pexels_url = 'https://api.pexels.com/v1/search'
       headers = {'Authorization': ' '}
       params = {'query': query, 'per_page': 10}
       response = requests.get(pexels_url, headers=headers, params=params)

        if response.status_code == 200:
           results = response.json()
           images = []
           for photo in results.get('photos', []):
               if photo and 'src' in photo:
                   images.append({
                       'title': photo.get('alt', 'Image'),
                       'url': photo['src'].get('medium', '')
                   })
       else:
           images = []
       return jsonify({'image': images})

    elif category == 'news':
       url = f"https://newsapi.org/v2/everything?q={query}&sortBy=publishedAt&language=en&apiKey=a55de15e171c43838e007ea6600495a8"
       response = requests.get(url)
       news_data = response.json()

        articles = [{
           'title': article['title'],
           'url': article['url'],
           'description': article.get('description'),
           'image': article.get('urlToImage')
       } for article in news_data.get('articles', [])[:6]]

        return jsonify({'news': articles})

 

Checks if the search category is 'image'. If so, the following code will run. pexels_url = 'https://api.pexels.com/v1/search'. Pexels API provides free stock free images (in this project, it will provide drone stock images).

Then with this,'    headers = {'Authorization': ' '}', it prepares the headers for the API request (The actual API Key inside of '  ' is deleted for security reason). params = {'query': query, 'per_page': 10} sets the parameters. 'query' is the search term and 'per_page' is the number of images to return. 
   
   elif category == 'video':
       api_key = os.getenv('YOUTUBE_API_KEY', '').strip("'")
       if not api_key:
           return jsonify({'video': [], 'error': 'YouTube API key not configured'})
           
       url = "https://www.googleapis.com/youtube/v3/search"
       params = {
           'part': 'snippet',
           'type': 'video',
           'q': query,
           'maxResults': 5,
           'key': api_key
       }
       
       try:
           response = requests.get(url, params=params)
           response.raise_for_status()
           data = response.json()
           
           videos = [{
               'title': item['snippet']['title'],
               'url': f"https://www.youtube.com/embed/{item['id']['videoId']}"
           } for item in data.get('items', [])]
           
           return jsonify({'video': videos})
       except Exception as e:
           print("YouTube API error:", str(e))
           return jsonify({'video': [], 'error': 'Failed to fetch videos'})

    else:
       return jsonify({'error': 'Unsupported category'})

In this snippet, 

"response = requests.get(pexels_url, headers=headers, params=params)" ->

 

response.status_code: The HTTP status code (e.g., 200 for success).
response.text: The raw response content as a string.
response.json(): The response content parsed as JSON (if the response is JSON).
response.headers: The response headers.

​response = the HTTP response object from the Pexels API, containing the results of your image search

And, in this code: 

if response.status_code == 200:
    results = response.json()
    images = []

The last line creates an empty list called 'images' which intends to store the image data (URLs, descriptions, etc.) extracted from the API response. So, images = [] initializes an empty list so it can collect and store the image data extracted from the API response. This is a common pattern for building up a list of items dynamically.

return jsonify({'image': images})

jsonify is a function (commonly from Flask, a Python web framework) that converts your Python data (like dictionaries and lists) into a JSON-formatted HTTP response.
{'image': images} creates a dictionary with a key 'image' and the value being the images list you built earlier.
return sends this JSON response back to whoever made the HTTP request to your server (like a frontend app or another API).

Why use jsonify? It automatically sets the correct Content-Type: application/json header. It safely serializes your Python data to JSON.

In case of video feature, it works in a similar way. 

-Builds the API request (URL, headers, params) for the relevant external service.
-Sends the request using requests.get(...).
-Processes the response (usually with .json()).
-Extracts and formats the data into a Python list or dictionary.
-Returns the data as JSON using jsonify(...).

Then, this project has two corresponding files: app.js and index.html

templates/index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>GoCool Search</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <script src="{{ url_for('static', filename='app.js') }}" defer></script>
    <style>
        .home-button {
            position: fixed;
            top: 20px;
            left: 20px;
            font-size: 24px;
            color: #3366cc;
            text-decoration: none;
            font-weight: bold;
            z-index: 1000;
        }

        .home-button:hover {
            color: #254b8a;
        }

        #auth-modal, #history-modal {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
            z-index: 1000;
        }

        #auth-container {
            position: absolute;
            top: 10px;
            right: 10px;
            padding: 10px;
            background: white;
            border-radius: 4px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }

        button {
            padding: 8px 16px;
            margin: 5px;
            border: none;
            border-radius: 4px;
            background: #3366cc;
            color: white;
            cursor: pointer;
        }

        button:hover {
            background: #254b8a;
        }

        input {
            padding: 8px;
            margin: 5px;
            border: 1px solid #ccc;
            border-radius: 4px;
            width: 200px;
        }

        .modal-content {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }

        body {
            background-color: #ffffff;
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            min-height: 100vh;
            margin: 0;
        }

        .search-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 20px;
            margin-top: 20vh;
        }

        .search-results {
            display: none;
            width: 90%;
            max-width: 1200px;
            margin-top: 20px;
        }

        .search-box {
            display: flex;
            gap: 10px;
        }

        h1 {
            font-size: 48px;
            color: #3366cc;
            margin-bottom: 20px;
        }

        .section {
            margin: 20px 0;
            padding: 20px;
            background: #f5f5f5;
            border-radius: 8px;
        }

        .section h2 {
            color: #3366cc;
            margin-bottom: 15px;
        }

        .results-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
        }

        .video-container {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
        }

        .video-item {
            text-align: center;
        }

        .video-item iframe {
            width: 100%;
            height: 200px;
            border: none;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <div id="auth-container">
        <div id="logged-out">
            <button onclick="showLoginForm()" style="background-color: #ff8c00;">Login</button>
            <button onclick="showSignupForm()">Signup</button>
        </div>
        <div id="logged-in" style="display: none;">
            <span style="margin-right: 10px;">Welcome, <span id="user-email"></span>!</span>
            <button onclick="logout()" style="background-color: #dc3545;">Logout</button>
            <button onclick="showSearchHistory()">History</button>
        </div>
    </div>

    <div class="search-container">
        <h1>GoCool</h1>
        <div class="search-box">
            <input type="text" id="searchInput" placeholder="Search anything..." value="Drone">
            <button onclick="performSearch()">Search</button>
        </div>
    </div>

    <a href="/" class="home-button">GoCool</a>
    <div class="search-results" id="searchResults">
        <div class="section">
            <h2>Web Results</h2>
            <div id="web-results"></div>
        </div>
        
        <div class="section">
            <h2>Images</h2>
            <div id="image-results" class="results-grid"></div>
        </div>
        
        <div class="section">
            <h2>Videos</h2>
            <div id="video-results" class="video-container"></div>
        </div>
        
        <div class="section">
            <h2>News</h2>
            <div id="news-results"></div>
        </div>
    </div>

    <div id="auth-modal" class="modal-content">
        <h2 id="auth-title">Login</h2>
        <input type="email" id="auth-email" placeholder="Email">
        <input type="password" id="auth-password" placeholder="Password">
        <div>
            <button onclick="submitAuth()">Submit</button>
            <button onclick="closeAuthModal()">Close</button>
        </div>
    </div>

    <div id="history-modal" class="modal-content">
        <h2>Search History</h2>
        <div id="history-content"></div>
        <button onclick="closeHistoryModal()">Close</button>
    </div>
</body>
</html>

 

Screenshot 2025-05-27 205857_edited.jpg
Screenshot 2025-05-28 210856.png

static/app.js

// Auth functions
function showLoginForm() {
    document.getElementById('auth-modal').style.display = 'block';
    document.getElementById('auth-title').textContent = 'Login';
}

function showSignupForm() {
    document.getElementById('auth-modal').style.display = 'block';
    document.getElementById('auth-title').textContent = 'Signup';
}

function closeAuthModal() {
    document.getElementById('auth-modal').style.display = 'none';
}

async function submitAuth() {
    const email = document.getElementById('auth-email').value;
    const password = document.getElementById('auth-password').value;
    const isLogin = document.getElementById('auth-title').textContent === 'Login';

    const response = await fetch(isLogin ? '/login' : '/signup', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
        credentials: 'include'
    });

    const data = await response.json();
    if (data.success) {
        if (isLogin && data.user) {
            document.getElementById('user-email').textContent = data.user.email;
            document.getElementById('logged-out').style.display = 'none';
            document.getElementById('logged-in').style.display = 'block';
        }
        closeAuthModal();
    } else {
        alert(data.error);
    }
}

async function logout() {
    await fetch('/logout', { method: 'POST', credentials: 'include' });
    document.getElementById('user-email').textContent = '';
    document.getElementById('logged-out').style.display = 'block';
    document.getElementById('logged-in').style.display = 'none';
}

async function checkAuthStatus() {
    try {
        const userEmail = document.getElementById('user-email');
        const loggedOut = document.getElementById('logged-out');
        const loggedIn = document.getElementById('logged-in');

        if (userEmail && userEmail.textContent) {
            loggedOut.style.display = 'none';
            loggedIn.style.display = 'block';
        } else {
            loggedOut.style.display = 'block';
            loggedIn.style.display = 'none';
        }
    } catch (error) {
        console.error('Error checking auth status:', error);
    }
}

async function showSearchHistory() {
    const response = await fetch('/search-history');
    const data = await response.json();

    const historyContent = document.getElementById('history-content');
    historyContent.innerHTML = data.history.map(item => `
        <div>
            <strong>${item.query}</strong> (${item.category})
            <small>${new Date(item.timestamp).toLocaleString()}</small>
        </div>
    `).join('');

    document.getElementById('history-modal').style.display = 'block';
}

function closeHistoryModal() {
    document.getElementById('history-modal').style.display = 'none';
}

async function performSearch() {
    const query = document.getElementById('searchInput').value || 'drone';
    document.getElementById('searchResults').style.display = 'block';

    // Fetch all categories in parallel
    const [webResponse, imageResponse, videoResponse, newsResponse] = await Promise.all([
        fetch('/websearch', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query })
        }),
        fetch('/search', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query, category: 'image' })
        }),
        fetch('/search', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query, category: 'video' })
        }),
        fetch('/search', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ query, category: 'news' })
        })
    ]);

    const [webData, imageData, videoData, newsData] = await Promise.all([
        webResponse.json(),
        imageResponse.json(),
        videoResponse.json(),
        newsResponse.json()
    ]);

    // Update Web Results
    const webResults = document.getElementById('web-results');
    webResults.innerHTML = webData.web.map(item => 
        `<p><a href="${item.url}" target="_blank">${item.title}</a></p>`
    ).join('');

    // Update Image Results
    const imageResults = document.getElementById('image-results');
    imageResults.innerHTML = imageData.image.map(item =>
        `<div><img src="${item.url}" alt="${item.title}" style="max-width:100%;"><p>${item.title}</p></div>`
    ).join('');

    // Update Video Results
    const videoResults = document.getElementById('video-results');
    videoResults.innerHTML = videoData.video.map(item =>
        `<div class="video-item">
            <iframe src="${item.url}" allowfullscreen></iframe>
            <p>${item.title}</p>
        </div>`
    ).join('');

    // Update News Results
    const newsResults = document.getElementById('news-results');
    newsResults.innerHTML = newsData.news.map(item =>
        `<div style="margin-bottom: 20px;">
            ${item.image ? `<img src="${item.image}" style="max-width:200px; margin-right:10px; float:left;">` : ''}
            <h3><a href="${item.url}" target="_blank">${item.title}</a></h3>
            <p>${item.description || ''}</p>
            <div style="clear:both;"></div>
        </div>`
    ).join('');
}

document.addEventListener('DOMContentLoaded', () => {
    checkAuthStatus();
    // Add enter key support for search
    document.getElementById('searchInput').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            performSearch();
        }
    });
});

// YouTube Video Fetcher (outside DOMContentLoaded)
async function fetchYouTubeVideos(query) {
    console.log('Fetching YouTube videos for query:', query);
    const response = await fetch('/search', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query, category: 'video' })
    });

    const data = await response.json();
    console.log('YouTube API Response:', data);

    const resultsDiv = document.getElementById("video-results");
    resultsDiv.innerHTML = "";
    resultsDiv.style.display = 'grid';

    if (data.video && data.video.length > 0) {
        data.video.forEach((item) => {
            const container = document.createElement("div");
            container.style.textAlign = 'center';

            const iframe = document.createElement("iframe");
            iframe.src = item.url;
            iframe.width = "100%";
            iframe.height = "200";
            iframe.allowFullscreen = true;
            iframe.style.marginBottom = '10px';

            const titleElem = document.createElement("p");
            titleElem.textContent = item.title;
            titleElem.style.margin = '0 0 20px 0';

            container.appendChild(iframe);
            container.appendChild(titleElem);
            container.appendChild(iframe);

            resultsDiv.appendChild(container);
        });
    } else {
        resultsDiv.innerHTML = "No videos found.";
    }
}

How these files integrated? 

<script src="{{ url_for('static', filename='app.js') }}" defer></script>

This line tells the browser to load and run the JS file (app.js) after the HTML is parsed. The 'defer' ensures the script after the DOM is ready. 

JavaScript code interacts with the HTML by:
Selecting elements by ID (e.g., document.getElementById('auth-modal'))
Changing their content or style (e.g., showing/hiding modals, updating results)
Responding to user actions (e.g., button clicks, form submissions)

For example, the Login and Signup Modal is like:

JavaScript code interacts with the HTML by:
Selecting elements by ID (e.g., document.getElementById('auth-modal'))
Changing their content or style (e.g., showing/hiding modals, updating results)
Responding to user actions (e.g., button clicks, form submissions)

JS:    

<div id="auth-modal" class="modal-content">
        <h2 id="auth-title">Login</h2>
        <input type="email" id="auth-email" placeholder="Email">
        <input type="password" id="auth-password" placeholder="Password">
        <div>
            <button onclick="submitAuth()">Submit</button>
            <button onclick="closeAuthModal()">Close</button>
        </div>
    </div>

HTML:

    <input type="text" id="searchInput" placeholder="Search anything..." value="Drone">
    <button onclick="performSearch()">Search</button>

The Search part:

JS: performSearch() reads the input, sends search requests, and updates the results sections.

    <div id="auth-modal" class="modal-content">
        <h2 id="auth-title">Login</h2>
        <input type="email" id="auth-email" placeholder="Email">
        <input type="password" id="auth-password" placeholder="Password">
        <div>
            <button onclick="submitAuth()">Submit</button>
            <button onclick="closeAuthModal()">Close</button>
        </div>
    </div>

HTML:

    <div id="web-results"></div>
    <div id="image-results" class="results-grid"></div>
    <div id="video-results" class="video-container"></div>
    <div id="news-results"></div>

Event listeners:

  document.getElementById('searchInput').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
          performSearch();
      }
  });

When the key is pressed (in the search box), it triggers a search.

The Full Flow - 
User loads the page
HTML is rendered.

JavaScript is loaded and runs after the DOM is ready.
User interacts with the page
Clicks Login/Signup: JS shows the modal.
Submits credentials: JS sends to backend, updates UI on success/failure.
Types a search and presses Enter or clicks Search: JS sends search requests, updates results sections.
JavaScript updates the HTML
Shows/hides modals.
Fills in search results.
Updates authentication state.

The HTML provides the structure and elements, while the JavaScript controls the behavior, interactivity, and dynamic content by manipulating those elements. The two files are tightly integrated through element IDs and event handlers.

Overall Evaluation of the Project
Strengths
Full-Stack Functionality:
The project demonstrates a working full-stack web application, with both frontend (HTML/CSS/JS) and backend (API integration, authentication, search history).
Users can search for images, news, videos, and web results, and see them organized in a clean UI.
API Integration:
Leverages multiple real-world APIs (Pexels, NewsAPI, YouTube, etc.) to fetch live data.
Shows practical experience in working with third-party services and handling their responses.
User Authentication:
Implements login, signup, and logout, with UI state changes based on authentication.
Demonstrates understanding of session management and user state.
Search History:
Remembers user searches and displays them, adding a personalized touch.
UI/UX:
Clean, modern interface with modals, sections, and responsive design.
Good use of JavaScript for dynamic updates and user feedback.

Shows how to build a modern web app, integrate APIs, manage authentication, and handle dynamic content.
Good stepping stone for more advanced projects.

Limitations
Not a True Search Engine:
The project is an aggregator of results from other APIs, not a crawler or indexer.
No web crawling, indexing, or ranking algorithms like Google or Bing.
Dependent on the limitations and quotas of third-party APIs.
Scalability:
Not designed for high traffic or large-scale deployment.
API keys are exposed to quota limits and possible abuse.
Data Depth and Quality:
Limited to what the APIs return (e.g., only a few results per query).
No advanced ranking, filtering, or personalization beyond what APIs provide.
Security:
Basic authentication, but may lack advanced security features (rate limiting, CSRF protection, etc.).
API keys may be hardcoded or not securely managed.
No Advanced Features:
No user profiles, saved searches, or advanced filtering.
No machine learning, semantic search, or natural language processing.

 

Potential future improvements?

Can be extended with more features, better security, or even a custom backend for crawling/indexing.
 

Screenshot 2025-05-29 200905.png
bottom of page