layout: page title: Sprint 5 Dynamic Checkpoint Blog course: compsci week: 23 type: hacks —

Full Stack Feature Implementation: Interests Management

Purpose of the Program

The purpose of this program is to provide a full-stack solution for managing user interests. Users can add, update, and delete their interests through a web interface, and the changes are reflected in the backend database.

Purpose of the Feature

The feature allows users to:

  • Add new interests
  • Update existing interests
  • Delete specific interests

Input/Output Requests

Demo: Input to Full Stack Feature

Using Frontend to Show API Request and Present API Response

<!-- filepath: /home/yashunix/nighthawk/prism/prism_frontend/navigation/authentication/profile.md -->
<script type="module">
import { pythonURI, fetchOptions } from '/yash_2025/assets/js/api/config.js';

// Function to update user interests
async function updateProfile(field, value) {
    try {
        if (field === 'interests' && value) {
            const response = await fetch(pythonURI + "/api/user", fetchOptions);
            const userData = await response.json();
            const currentInterests = userData.interests ? userData.interests.split(',').map(i => i.trim()) : [];
            const newInterests = value.split(',').map(i => i.trim());
            const combinedInterests = [...new Set([...currentInterests, ...newInterests])];
            value = combinedInterests.join(', ');

            const updateResponse = await fetch(pythonURI + "/api/interests", {
                ...fetchOptions,
                method: 'PUT',
                body: JSON.stringify({ interests: value })
            });

            if (!updateResponse.ok) {
                throw new Error('Failed to update interests');
            }

            showError('Interests updated successfully', 'green');
            updateUserInfo();
            return;
        }
    } catch (error) {
        console.error('Error updating profile:', error);
        showError('Error updating profile');
    }
}

// Attach the updateProfile function to the window object
window.updateProfile = updateProfile;
</script>

Using Postman to Show Raw API Request and RESTful Response

POST Request to Add Interests:

  • URL: http://localhost:5000/api/interests
  • Method: POST
  • Body:
    {
      "interests": "Reading, Coding, Hiking"
    }
    

Response:

  • Status: 200 OK
  • Body:
    {
      "message": "Interests added successfully",
      "interests": "Reading, Coding, Hiking"
    }
    

DELETE Request to Remove an Interest:

  • URL: http://localhost:5000/api/interests
  • Method: DELETE
  • Body:
    {
      "interest": "Coding"
    }
    

Response:

  • Status: 200 OK
  • Body:
    {
      "message": "Interest deleted successfully",
      "interests": "Reading, Hiking"
    }
    

CRUD Postman Response

alt text

alt text

alt text

alt text

alt text

alt text

Using db_init

# filepath: /home/yashunix/nighthawk/prism/prism_backend/db_init.py
from __init__ import db
from model.user import User

# Initialize the database
db.create_all()

# Add sample data
user = User(name="John Doe", uid="johndoe", interests="Reading, Coding, Hiking")
db.session.add(user)
db.session.commit()

print("Database initialized with sample data.")

Using db_backup


# Backup the old database
def backup_database(db_uri, backup_uri):
    """Backup the current database."""
    if backup_uri:
        db_path = db_uri.replace('sqlite:///', 'instance/')
        backup_path = backup_uri.replace('sqlite:///', 'instance/')
        shutil.copyfile(db_path, backup_path)
        print(f"Database backed up to {backup_path}")
    else:
        print("Backup not supported for production database.")

Using db_restore


    @staticmethod
    def restore(data):
        users = {}
        for user_data in data:
            _ = user_data.pop('id', None)  # Remove 'id' from user_data and store it in user_id
            uid = user_data.get("uid", None)
            user = User.query.filter_by(_uid=uid).first()
            if user:
                user.update(user_data)
            else:
                user = User(**user_data)
                user.create()
        return users

List Requests

Use of List, Dictionaries, and Database

Code Description:

  • Lists: Used to manage user interests.
  • Dictionaries: Used to handle JSON data in API requests and responses.
  • Database: Used to store user data, including interests.

Formatting Response Data (JSON) from API into DOM:

// filepath: /home/yashunix/nighthawk/prism/prism_frontend/navigation/authentication/profile.md
function createInterestCards(interests) {
    const interestsSection = document.getElementById('interestsSection');
    interestsSection.innerHTML = '';
    
    interests.forEach(interest => {
        const card = document.createElement('div');
        card.className = 'card';
        card.innerHTML = `
            <h4>${interest}</h4>
            <img src="/yash_2025/assets/images/placeholder.png" alt="${interest}">
            <button onclick="deleteInterest('${interest}')">Delete</button>
        `;
        interestsSection.appendChild(card);
    });
}

Queries from Database: - These are provided by SQLAlchemy ORM, a third-party library, which simplifies the process of using Python to interact with databases.

The filter_by method, provided by SQLAlchemy, retrieves all interests associated with a UID (user id).

# filepath: /home/yashunix/nighthawk/prism/prism_backend/api/interests.py
@token_required()
def get(self):
    """
    Return the interests of the authenticated user as a JSON object.
    """
    current_user = g.current_user
    user = User.query.filter_by(_uid=current_user.uid).first()
    if not user or not user.interests:
        return {'message': 'No interests found for this user'}, 404
    return jsonify(user.interests)

Methods in Class to Work with Columns:

# filepath: /home/yashunix/nighthawk/prism/prism_backend/api/interests.py
@token_required()
def post(self):
"""
Create interests for the authenticated user.
"""
current_user = g.current_user
user = User.query.filter_by(_uid=current_user.uid).first()
if not user:
    return {'message': 'User not found'}, 404

body = request.get_json()
new_interests = body.get('interests')
if not new_interests:
    return {'message': 'No interests provided'}, 400

formatted_interests = re.sub(r'\s*,\s*', ', ', new_interests.strip())
user.interests = formatted_interests
user.update({'interests': user.interests})
return jsonify(user.interests)

Algorithmic Code Request

Definition of Code Blocks to Handle a Request

API Class to Perform CRUD Methods:

These methods handle the Create, Read, Update, and Delete operations for user interests. They interact with the database to manage the interests associated with a user. The methods are protected because they require a token to ensure that only authenticated users can access them.

# filepath: /home/yashunix/nighthawk/prism/prism_backend/api/interests.py
    class _CRUD(Resource):
        """
        Interests API operation for Create, Read, Update, Delete.
        """

        @token_required()
        def get(self):
            """
            Return the interests of the authenticated user as a JSON object.
            """
            current_user = g.current_user
            user = User.query.filter_by(_uid=current_user.uid).first()
            if not user or not user.interests:
                return {'message': 'No interests found for this user'}, 404
            return jsonify(user.interests)

        @token_required()
        def post(self):
            """
            Create interests for the authenticated user.
            """
            current_user = g.current_user
            user = User.query.filter_by(_uid=current_user.uid).first()
            if not user:
                return {'message': 'User not found'}, 404

            body = request.get_json()
            new_interests = body.get('interests')
            if not new_interests:
                return {'message': 'No interests provided'}, 400

            formatted_interests = re.sub(r'\s*,\s*', ', ', new_interests.strip())
            user.interests = formatted_interests
            user.update({'interests': user.interests})
            return jsonify(user.interests)

        @token_required()
        def put(self):
            """
            Update and add to the interests of the authenticated user.
            """
            current_user = g.current_user
            user = User.query.filter_by(_uid=current_user.uid).first()
            if not user:
                return {'message': 'User not found'}, 404

            body = request.get_json()
            new_interests = body.get('interests')
            if not new_interests:
                return {'message': 'No new interests provided'}, 400

            formatted_new_interests = re.sub(r'\s*,\s*', ', ', new_interests.strip())
            current_interests = user.interests.split(', ') if user.interests else []
            combined_interests = list(set(current_interests + formatted_new_interests.split(', ')))
            user.interests = ', '.join(combined_interests)
            user.update({'interests': user.interests})
            return jsonify(user.interests)

        @token_required()
        def delete(self):
            """
            Delete a specified interest of the authenticated user.
            """
            body = request.get_json()

            if not body or 'interest' not in body:
                return {'message': 'No interest provided'}, 400
            
            current_user = g.current_user
            interest_to_delete = body.get('interest')
            interests = current_user.interests.split(', ')

            if interest_to_delete not in interests:
                return {'message': 'Interest not found'}, 404

            interests.remove(interest_to_delete)
            current_user.interests = ', '.join(interests)
            current_user.update({'interests': current_user.interests})

            return {'message': 'Interest deleted successfully'}, 200

CRUD Postman Response

alt text

alt text

alt text

alt text

alt text

alt text

Method/Procedure in Class with Sequencing, Selection, and Iteration

Example:

# filepath: /home/yashunix/nighthawk/prism/prism_backend/api/user.py
@token_required()
def put(self):
"""
Update a user.
"""
current_user = g.current_user
body = request.get_json()

if 'followers' in body:
    new_followers = body['followers'].split(',')
    valid_followers = []
    for follower in new_followers:
        follower = follower.strip()
        if User.query.filter_by(_uid=follower).first():
            valid_followers.append(follower)
        else:
            return {'message': f'Follower {follower} does not exist'}, 400
    body['followers'] = ', '.join(valid_followers)

user.update(body)

return jsonify(user.read())

Parameters and Return Type

Parameters:

  • body: JSON object containing the interest to be deleted.

Return Type:

  • JSON response with a message indicating the result of the operation.

Call to Algorithm Request

Definition of Code Block to Make a Request

Frontend Fetch to Endpoint:

// filepath: /home/yashunix/nighthawk/prism/prism_frontend/navigation/authentication/profile.md
async function deleteInterest(interest) {
    try {
        const response = await fetch(pythonURI + "/api/interests", {
            ...fetchOptions,
            method: 'DELETE',
            body: JSON.stringify({ interest: interest })
        });

        if (!response.ok) {
            throw new Error('Failed to delete interest');
        }

        showError('Interest deleted successfully', 'green');
        updateUserInfo();
    } catch (error) {
        console.error('Error deleting interest:', error);
        showError('Error deleting interest');
    }
}

window.deleteInterest = deleteInterest;

Discuss the Call/Request to the Method with Algorithm

Call/Request:

  • The deleteInterest function sends a DELETE request to the /api/interests endpoint with the interest to be deleted.

Return/Response:

  • The response from the backend is handled by checking the status code and updating the UI accordingly.

Handling Data and Error Conditions

Normal Conditions:

  • The interest is successfully deleted, and the UI is updated to reflect the change.

Error Conditions:

  • If the interest is not found or the request fails, an error message is displayed.

alt text

alt text

College Board CPT Requirements

The implementation of the full-stack feature for managing user interests meets the following College Board CPT requirements:

Requirement A list or database A procedure A call to the procedure Selection Iteration
User Profile Map X X X X X

Conclusion

This blog demonstrates how to implement a full-stack feature for managing user interests. The feature includes adding, updating, and deleting interests, with proper handling of input/output requests, database interactions, and error conditions. By following the outlined steps, users can easily manage their interests through a web interface, with changes reflected in the backend database.