Intro to my work:

My CPT project from last tri, and what I integrated into our data structures site, was a video uploading/sharing site where users could login, upload, and view videos through our frontend. Our videos and user information were stored in the backend as data in our DB as well as various static files in the /videos directory.

Collections

We used an SQLite DB to store our videos for our CPT integration. Each row contains: A video ID, name, thumbnail image(stored using base 64), description, video owner(denoted by user ID), genre, views, likes, and dislikes. This is a lot of data to store, so in order to be scalable using a DB was the best option. SQLite pic Here is the initVideos function that we use to initialize the DB with our 2 example videos: sfsddg In our create() method we take in the data given and then add it to the DB, including using our static /videos directory to ensure the videos are stored.

Lists and Dictionaries

In the GET method we get the video database info and return it as JSON, by converting it to a list of dictionaries: This is just a simple Python list of all the videos and their corresponding metadata, being returned by the API to the frontend via JSON. sdkjgf Use of dictionary #1 – initializing DB with example video: This dictionary is created from the Video class – it adds the default videos in the site to the DB. tolfgm Use of dictionary #2 – GET method uses video DB table to send video data, along with metadata like description and views to the frontend using conversion to dictionary: dsogjnp

APIs and JSON

Python API definitions for API and CRUD methods:

class VideoAPI:        
    class _CRUD(Resource):  # User API operation for Create, Read.  THe Update, Delete methods need to be implemeented
        def put(self):
            body = request.get_json()
            type = int(body.get('type'))
            videoID = int(body.get('videoID'))
            if videoID is None:
                return {'message': f'Video ID is missing'}, 400
            video = Vid.query.filter_by(_videoID=videoID).first()
            if video:
                if type == 0:
                    try:
                        put_req = video.put()
                        return jsonify(video.read())
                    except Exception as e:
                        return {
                            "error": "Something went wrong",
                            "message": str(e)
                        }, 500
                elif type == 1:
                    try:
                        put_req = video.like()
                        return jsonify(video.read())
                    except Exception as e:
                        return {
                            "error": "Something went wrong",
                            "message": str(e)
                        }, 500
                elif type == 2:
                    try:
                        put_req = video.dislike()
                        return jsonify(video.read())
                    except Exception as e:
                        return {
                            "error": "Something went wrong",
                            "message": str(e)
                        }, 500
                
                
        @token_required
        def post(self, current_user): # Create method
            ''' Read data for json body '''
            if request.is_json:
                body = request.get_json()
                ''' Avoid garbage in, error checking '''
                name = body.get('name')
                if name is None:
                    return {'message': f'name is missing, or is less than 2 characters'}, 400

                description = body.get('description')
                if description is None:
                    return {'message': f'Description is missing, or is less than 2 characters'}, 400

                # look for password and dob
                base64 = body.get('base64')
                if base64 is None:
                    return {'message': f'Thumbnail is missing or in the wrong format'}, 400

                video = body.get('video')
                if video is None:
                    return {'message': f'Video is missing or in the wrong format'}, 400
                
                userID = body.get('uid')
                if userID is None:
                    return {'message': f'userID is missing or in the wrong format'}, 400
                
                thumb_name = body.get('thumbnail')
                if thumb_name is None:
                    return {'message': f'Thumbnail name is missing or in the wrong format'}, 400

                genre = body.get('genre')
                print(genre)
                if thumb_name is None:
                    return {'message': f'Genre  is missing or in the wrong format'}, 400
                
                ''' #1: Key code block, setup USER OBJECT '''
                vid = Vid(name=name, thumbnail=thumb_name,description=description,video=video,userID=userID,views=0,genre=genre)
                # create video in database
                videoJ = vid.create(base64)
                # success returns json of video
                if videoJ:
                    return jsonify(videoJ.read())
                # failure returns error
                return {'message': f'Processed {name}, either a format error or  ID {id} is duplicate'}, 400
            
            else:
                video_file = request.files['video']
                # Check if the file has a filename
                if video_file.filename == '':
                    return 'No selected file', 400

                # Save the video file to the 'videos' directory
                video_userID = os.path.join('videos', video_file.filename)
                video_file.save(video_userID)

        def get(self): # Read Method
            videos = Vid.query.all()    # read/extract all users from database
            json_ready = [video.read() for video in videos]  # prepare output in json
            return jsonify(json_ready)  # jsonify creates Flask response object, more specific to APIs than json.dumps
        
    class _ReadVID(Resource):
        def get(self, vid):
            video = Vid.query.filter_by(_videoID=vid).first()
            data = video.read()
            return jsonify(data)
        
        # def put(self,)

    class _Recommend(Resource):
        def get(self, uid):
            # Get user preferences
            user = Users.query.filter_by(_uid=uid).first()
            if user is None:
                return jsonify({"message": "User not found"}), 404
            
            user_preferences = user.preferences

            # Get all videos
            videos = Vid.query.all()

            # Filter videos based on matching genres
            matching_videos = []
            for video in videos:
                if any(pref in video.genre for pref in user_preferences):
                    matching_videos.append(video)

            # Calculate like to dislike ratio
            for video in matching_videos:
                if video.dislikes != 0:
                    video.like_to_dislike_ratio = video.likes / video.dislikes
                else:
                    video.like_to_dislike_ratio = video.likes

            sorted_videos = sorted(matching_videos, key=lambda x: x.views, reverse=True)

            # Sort matching videos based on like to dislike ratio
            sorted_videos = sorted(sorted_videos, key=lambda x: x.like_to_dislike_ratio, reverse=True)

            # Sort by highest to lowest views within each ratio group

            # Prepare JSON response
            json_ready = [video.read() for video in sorted_videos]
            return jsonify(json_ready)

Defining the above as an API resource allows Flask to add each path with the methods specified in the function names, so def post() and def get() would add the functions as POST request and GET request handling in the Flask app, respectively. These functions are designed to handle the methods given their name; the @token_required is used for methods that require a login(like uploading videos)

The following section of code validates the POST JSON data to make sure formatting is correct(making sure that the body has all fields necessary to complete the POST and making sure that the data given is not invalid). If not, then an error is raised and JSON is sent back to the request that describes the specific error that caused the POST to fail: sjgjnods

Postman

PUT: This put request adds 1 the view count of a video(telling the backend that a user has viewed this video); here I used Postman to add a view to an example video with the ID of 2. asdgjksng GET: This GET request recieves all of the videos currently in the video DB; in Postman here you can see the returned base64 data for the thumbnail image in the first video. adghfb POST: This POST request does a login to the backend, which returns a jwt cookie for persistent authentication on the actual frontend. This JWT cookie ensures security and allows users to stay logged in through all the pages in the frontend. sakjgnsdlg 400 Error with POST request(missing userID): This POST request is missing a userID for logging in, so the backend returns a 400 Bad Request HTTP error. sdlgnsil 404 Error with unrecognized video ID in view update PUT: When a video ID like 24(in this case, no video with ID 24 exists), the backend returns a 404 not found error, since no video was found that matches the ID of 24. psguonpo

Frontend

Browser Inspect: GET: This is the inspect window showing the network traffic(packet returned from the backend) when doing a GET request by loading the main video page in the frontend. safgjnsgk POST: The POST login returns a 200 OK response and a JWT token. sgkntrebhen PUT: The PUT request updates the views and returns the updated video data to confirm with the frontend or whoever is accessing the backend. dosjdgnpio

Formatting of Videos in view_videos.html – Videos are formatted in grid with thumbnails, titles, and view counts: sdgosngb

The following code:

  • Fetches all the videos from the backend Flask API endpoint using a JavaScript GET Fetch
  • Renders the videos in a grid format, with the proper title, thumbnail, view counts, and href redirects to access the specific videos
  • Encases everything in a try/catch for error handling, in case something goes wrong
%%javascript
// Fetch all the videos using GET
try {
        const apiUrl = "http://127.0.0.1:6221/api/video"
        const response = await fetch(apiUrl);
        const videos = await response.json();
        const query = document.getElementById("query").value;
        clearVideos()
        renderVideos(videos, query);
    } catch (error) {
        console.error('Error loading videos:', error);
    }


// Following JS code formats all the videos into a grid format, displaying the images as well
videos.forEach(video => {
    const videoItem = document.createElement('div');
    videoItem.classList.add('grid-item');
    videoItem.classList.add('video-item');

    const videoLink = document.createElement('a');
    videoLink.href = "video.html" + "?videoID=" + video["videoID"]; // Assuming video object has a 'video' property for the URL

    const videoImage = document.createElement('img');
    videoImage.classList.add('video');

    var encode = `${video.base64}`;
    var step1 = encode.replace(/\\n/g, '');
    var step2 = step1.replace(/b'/g, '');
    var base64String = step2.replace(/[-:'\\]/g, '');
    videoImage.src = `data:image/png;base64,${base64String}`;
    const title = document.createElement('span');

    title.textContent = video.name; // Assuming video object has a 'name' property for the title
    const viewCount = document.createElement('span');
    viewCount.textContent = `Views: ${video.views}`; // Assuming video object has a 'views' property
    viewCount.classList.add('view-count'); // Add a class for styling
    const lineBreak = document.createElement('br');

    // Append the videoLink to the videoItem
    videoItem.appendChild(videoLink);

    // Append the videoImage inside the videoLink
    videoLink.appendChild(videoImage);

    // Append the title, view count, and line break to the videoItem
    videoItem.appendChild(title);
    videoItem.appendChild(lineBreak);
    videoItem.appendChild(viewCount);

    // Append the videoItem to the videoGrid
    videoGrid.appendChild(videoItem);

    });

Add view on video load: Inspect request: This is how the inspect window looks upon a successful PUT request from the frontend to the backend. dasg Frontend update: The HTML on the frontend updates the views on the page load. asfkedsngsdfgsl Invalid PUT Request: An incorrectly formatted PUT request or malformed packet will return an error from the backend. afgjnsbg

Code:

  • Reloads view count and loads the video on-screen in the webpage
  • Uses a fetch statement to send a PUT request to the proper Flask API endpoint, with the correct videoID
  • Uses a try/catch on the fetch statement for error handling
%%javascript
// Display views on success
views.innerText = data["views"] + " views";
source.src = data["video"];
video.load();
// Actual fetch request
try {
    const url = "http://127.0.0.1:6221/api/video/"
    fetch(url, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ videoID: videoID, type: '0' }) //JSON data
    })
} catch (error) {
    // Log error message for reference
    console.error('Error:', error);
}