Nikhil
CRUD Integration Checkpoint (01/27/2025)
Executive Summary:
- Made a database to store user chess games and the moves played in the form of PGN files.
- Created a REST api with POST, GET, PATCH, and DELETE requests to manipulate data.
- Created manager page on frontend to review, delete, and update games.
- Created function request to save games automatically after completion.
Team Purpose:
- Chess social platform to discuss, analyze and learn chess content. Our project will be a social media website and forum where users can discuss chess topics and interact with Artificial Intelligence to learn and play chess.
Individual Features:
- The purpose of my feature for this review is to store games the user plays against the computer, this enables future review and analysis using the analysis page. The manager page employs CRUD functions so the user can manage the data.
- The game feature enables the user to play chess and provide data and topics of discussion for the users.
Input/Output requests:
- The PGN API has GET, POST, PATCH, and DELETE requests. The GET request is seen in the manager when the page is loaded as all the games are populated in cards, the POST request is made on the game page immediately after the game is over. The PATCH request is used to update the database entries, a PATCH request is used instead of a PUT as a PUT typically means all fields are changed, but for my data that would be illogical do I developed a PATCH method instead. Lastly, the DELETE request can also be used from the manager page. (to be shown in the live demo)
class _BULK_CRUD(Resource):
def get(self):
pgns = Pgn.query.all()
json_ready = []
for pgn in pgns:
pgn_data = pgn.read()
json_ready.append(pgn_data)
return jsonify(json_ready)
class _CRUD(Resource):
def post(self):
body = request.get_json()
body_pgn = body['pgn']
body_name = body['name']
body_id = body['user_id']
### TODO: Add error handling error for various input fields
pgn_obj = Pgn(pgn=body['pgn'], date=body['date'], name=body['name'], user_id=body['user_id'])
pgn_obj.create()
# if not pgn: # failure returns error message
# return {'message': f'Processed {body_pgn}, either a format error or User ID {body_id} is duplicate'}, 400
return jsonify(pgn_obj.read())
def delete(self):
body = request.get_json()
pgn = Pgn.query.get(body['id'])
if not pgn:
return {'message': 'Pgn not found'}, 404
pgn.delete()
return jsonify({"message": "pgn deleted"})
def patch(self):
body = request.get_json()
pgn = Pgn.query.get(body['id'])
if pgn is None:
return {'message': 'Pgn not found'}, 404
if 'name' in body:
pgn._name = body['name']
pgn.patch()
return jsonify(pgn.read())
- Postman requests work as well, and if breakpoint is placed on the return line the function will cease to function, displaying proper setup.
Requests & DOM:
- The fetch request receives data from the REST API and is manipulated in the DOM (Document Object Model). Looping through the response object, and then accessing each property of each individual “game” to then be displayed.
async function fetchGames(){
try {
const response = await fetch(`${pythonURI}/api/pgns`, fetchOptions);
if (!response.ok) {
throw new Error('Failed to fetch groups: ' + response.statusText);
}
const games = await response.json();
const container = document.getElementById('cards-container');
container.innerHTML = '';
games.forEach(game => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<h3>${game.name}</h3>
<p>${game.pgn}</p>
<button class="button update">Update</button>
<button class="button delete">Delete</button>
<button class="button analyze">Analyze</button>
`;
card.querySelector('.delete').addEventListener('click', () => deleteGame(game.id));
card.querySelector('.update').addEventListener('click', () => updateGame(game.id));
card.querySelector('.analyze').addEventListener('click', () => redirectToAnalyze(game.id));
container.appendChild(card);
});
} catch (error) {
console.error('Error fetching entries:', error);
}
}
async function deleteGame(id) {
try {
const response = await fetch(`${pythonURI}/api/pgn`, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: id })
});
if (!response.ok) {
throw new Error('Failed to delete: ' + response.statusText);
}
fetchGames();
} catch (error) {
console.error('Error deleting entry:', error);
}
}
async function updateGame(id) {
const newName = prompt("Enter the new name for the game:");
try {
const response = await fetch(`${pythonURI}/api/pgn`, {
method: 'PATCH',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: id, name: newName })
});
if (!response.ok) {
throw new Error('Failed to delete: ' + response.statusText);
}
fetchGames();
} catch (error) {
console.error('Error deleting entry:', error);
}
}
async function makeGame(pgn) {
try {
const response = await fetch(`${pythonURI}/api/pgn`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ pgn: pgn, date: '01/23/2025', name: 'placeholder', user_id: 3 })
});
if (!response.ok) {
throw new Error('Failed to delete: ' + response.statusText);
}
} catch (error) {
console.error('Error deleting entry:', error);
}
}
- The queries to database are made by Flask_SQLAlchemy which is an ORM (Object Relational Manager) that abstracts the SQL requests enabling easier interfacing with the database.
def delete(self):
try:
db.session.delete(self)
db.session.commit()
except Exception as e:
db.session.rollback()
raise e
def patch(self):
inputs = Pgn.query.get(self.id)
if inputs._pgn:
self._pgn = inputs._pgn
if inputs._date:
self._date = inputs._date
if inputs._name:
self._name = inputs._name
try:
db.session.commit()
except IntegrityError:
db.session.rollback()
logging.warning(f"IntegrityError: Could not update pgn entry.")
return None
return self
- The classes in my API file have multiple methods that can be called, most process the request data, parse it, perform the associated database operation and return a response. The database operations that create, delete, read, etc, are defined in the model file.
Algorithmic Request:
- My “_CRUD” class handles POST, DELETE, and PATCH Requests. Based on the fetch request parameters one of the methods is called the corresponding logic executed. The function take the request data, parse it if necessary call the database function required and return a response (status like 200, or 404, or something else.)
- The delete method, obtains the entry id to be deleted from the fetch request, then it searches through the database to find the entry and delete it. The development of the function involved iteration, as I tested what parameters to be sent from frontend, in the end the entry id was most logical and efficient.
- The body of the request contains the id, and the response contains a status message and status code. Should the call be successful, it will say “PGN deleted” with a 200 status code, else it returns “PGN could not be found” with a 404 error code.
Call to Algorithm Request:
- The delete request is called when a click is made on the delete button, an event listener listens for a click action and calls the delete function accordingly which makes the fetch request. The fetch makes a DELETE call to /api/pgn and passes a body with an id to be deleted. The fetch request is a try-except block to handle errors.
- After the response is returned, should there be no issues the function that runs the GET request will be called to update the page after the delete request, else error messages are printed.
- The sole parameter is the entry’s id, and the jsonify response is a a string indicating success or error along with the corresponding HTTP status code.
async function updateGame(id) {
const newName = prompt("Enter the new name for the game:");
try {
const response = await fetch(`${pythonURI}/api/pgn`, {
method: 'PATCH',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: id, name: newName })
});
if (!response.ok) {
throw new Error('Failed to delete: ' + response.statusText);
}
fetchGames();
} catch (error) {
console.error('Error deleting entry:', error);
}
}
Vasanth
CRUD Integration Checkpoint (01/27/2025)
Executive Summary:
Developed a system for users to track and manage their skills.
Team Purpose:
Our project provides a social platform for users to track and improve their skills, manage profiles, and track progress.
Individual Features:
For this review, I focused on the skills feature, where users can input, track, update, and delete their skills. This feature is essential for users to document and modify their skill sets easily, with functionality including creating new skills, updating skill details, and deleting skills they no longer wish to track. The frontend allows users to interact with this system through forms and buttons for the CRUD operations, while the backend stores and processes all data for persistence.
Input/Output requests:
The Skills API supports the basic CRUD operations:
- GET: Retrieves a list of all skills or a specific skill by ID. This request is handled when the page loads or when a user requests a specific skill.
- POST: Allows users to add new skills to the database. This is done through the form on the frontend, where the user submits the skill name, skill level, and user ID.
- PUT: Enables users to update existing skills. If a user wishes to update their skill name or skill level, they can modify it using the “Update” button, which sends a PUT request to the API.
- DELETE: Removes a specific skill from the database. The user can delete a skill by clicking the “Delete” button next to the corresponding entry.
Requests are handled by Flask_SQLAlchemy, which uses an ORM to manage the interaction with the database, allowing easy retrieval and manipulation of skill data.
Requests & DOM:
The fetch requests interact with the backend API using the POST
, PUT
, GET
, and DELETE
methods. After fetching data from the API, the frontend dynamically populates a table to display the skill data. This table allows the user to view and interact with each entry, using buttons for adding, updating, and deleting skills.
The database operations are managed by SQLAlchemy in the backend, which abstracts the SQL queries and simplifies working with the data. The logic for the CRUD functions is handled in the SkillAPI
class, where each method (POST, GET, PUT, DELETE) processes the request, interacts with the database, and sends back a response to the frontend.
const API_URL = 'http://127.0.0.1:8887/api/skill'; // Replace with your actual API endpoint
// Fetch skills from the database and display them in the table (GET)
async function fetchSkills() {
try {
// Make a GET request to fetch all skills from the API
const response = await fetch(API_URL, { method: 'GET' });
// Handle HTTP errors
if (!response.ok) throw new Error('Failed to fetch skills.');
// Parse the JSON response
const skills = await response.json();
// Get the skills table element and clear any existing rows
const table = document.getElementById('skillsTable');
table.innerHTML = ''; // Clear table before populating
// Add each skill as a row in the table
skills.forEach(skill => addRowToTable(skill.id, skill.skill_name, skill.skill_level));
} catch (error) {
console.error('Error fetching skills:', error);
}
}
// Add a new skill to the database and table (POST)
async function addSkill(event) {
event.preventDefault(); // Prevent form submission to avoid page refresh
// Retrieve values from input fields
const idInput = document.getElementById('idInput').value.trim();
const skillInput = document.getElementById('skillInput').value.trim();
const rankInput = document.getElementById('rankInput').value.trim();
// Validate input fields
if (idInput && skillInput && rankInput) {
const newSkill = {
skill_name: skillInput, // Skill name
skill_level: rankInput, // Skill level
user_id: parseInt(idInput, 10), // User ID (convert string to integer)
};
try {
// Make a POST request to add the new skill
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newSkill),
});
// Handle HTTP errors
if (!response.ok) throw new Error('Failed to add skill.');
// Parse the response and update the table
const result = await response.json();
addRowToTable(result.id, result.skill_name, result.skill_level);
// Clear input fields after successful submission
document.getElementById('idInput').value = '';
document.getElementById('skillInput').value = '';
document.getElementById('rankInput').value = '';
} catch (error) {
console.error('Error adding skill:', error);
}
}
}
// Add a new row to the table (helper function)
function addRowToTable(id, skillName, level) {
const table = document.getElementById('skillsTable');
const row = table.insertRow(); // Create a new row in the table
// Insert cells for ID, Skill Name, and Level
row.insertCell(0).textContent = id;
row.insertCell(1).textContent = skillName;
row.insertCell(2).textContent = level;
// Create a cell for action buttons (Update and Delete)
const actionCell = row.insertCell(3);
actionCell.classList.add('action-buttons');
// Create and configure the Update button
const updateButton = document.createElement('button');
updateButton.textContent = 'Update';
updateButton.classList.add('update-button');
updateButton.onclick = () => updateRow(row, id);
// Create and configure the Delete button
const deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.classList.add('delete-button');
deleteButton.onclick = () => deleteRow(row, id);
// Append the buttons to the action cell
actionCell.appendChild(updateButton);
actionCell.appendChild(deleteButton);
}
// Update a skill in the database and table (PUT)
async function updateRow(row, id) {
// Get the current values from the row
const skillCell = row.cells[1];
const levelCell = row.cells[2];
// Prompt the user for new values
const newSkillName = prompt('Update Skill Name:', skillCell.textContent);
const newLevel = prompt('Update Level:', levelCell.textContent);
// If the user provides valid inputs, proceed with the update
if (newSkillName !== null && newLevel !== null) {
const updatedSkill = {
id: id, // Skill ID
skill_name: newSkillName.trim(), // New skill name
skill_level: newLevel.trim(), // New skill level
user_id: parseInt(id, 10), // User ID (convert string to integer)
};
try {
// Make a PUT request to update the skill
const response = await fetch(`${API_URL}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedSkill),
});
// Handle HTTP errors
if (!response.ok) throw new Error('Failed to update skill.');
// Update the table row with the new values
skillCell.textContent = updatedSkill.skill_name;
levelCell.textContent = updatedSkill.skill_level;
} catch (error) {
console.error('Error updating skill:', error);
}
}
}
// Delete a skill from the database and remove the row (DELETE)
async function deleteRow(row, id) {
// Confirm the deletion with the user
if (confirm('Are you sure you want to delete this skill?')) {
try {
// Make a DELETE request to remove the skill
const response = await fetch(`${API_URL}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id }), // Send the skill ID in the request body
});
// Handle HTTP errors
if (!response.ok) throw new Error('Failed to delete skill.');
// Remove the row from the table on success
row.remove();
} catch (error) {
console.error('Error deleting skill:', error);
}
}
}
// Attach event listener to the form for adding skills
document.getElementById('addSkillForm').addEventListener('submit', addSkill);
// Fetch and display skills when the page loads
window.onload = fetchSkills;
Algorithmic Request:
The _CRUD
class within the API handles the main operations: POST, PUT, and DELETE requests. When the frontend triggers one of these actions (e.g., clicking the “Add Skill” button or updating a skill), the corresponding method in the class is executed. Each method takes the necessary input data, processes it, and calls the appropriate function to interact with the database.
class SkillAPI:
class _CRUD(Resource):
def post(self):
"""
Create a new skill entry.
"""
data = request.get_json()
# Validate required fields
if not data or 'skill_name' not in data or 'skill_level' not in data or 'user_id' not in data:
return {'message': 'Skill Name, Level, and User ID are required.'}, 400
# Create a new skill
skill = Skill(
skill_name=data.get('skill_name'),
skill_level=data.get('skill_level'),
user_id=data.get('user_id')
)
try:
skill.create()
return jsonify(skill.read())
except Exception as e:
return {'message': f'Error saving skill: {e}'}, 500
def get(self):
"""
Retrieve a skill by ID or all skills.
"""
skill_id = request.args.get('id')
# Fetch a specific skill if ID is provided
if skill_id:
skill = Skill.query.get(skill_id)
if not skill:
return {'message': 'Skill not found'}, 404
return jsonify(skill.read())
# Fetch all skills
all_skills = Skill.query.all()
return jsonify([skill.read() for skill in all_skills])
def put(self):
"""
Update an existing skill entry.
"""
data = request.get_json()
# Validate required fields
if not data or 'id' not in data:
return {'message': 'ID is required for updating a skill'}, 400
# Find the skill by ID
skill = Skill.query.get(data['id'])
if not skill:
return {'message': 'Skill not found'}, 404
# Update the skill
try:
if 'skill_name' in data:
skill._skill_name = data['skill_name']
if 'skill_level' in data:
skill._skill_level = data['skill_level']
if 'user_id' in data:
skill._user_id = data['user_id']
skill.create() # Save changes
return jsonify(skill.read())
except Exception as e:
return {'message': f'Error updating skill: {e}'}, 500
def delete(self):
"""
Delete a skill entry.
"""
data = request.get_json()
# Validate required fields
if not data or 'id' not in data:
return {'message': 'ID is required for deleting a skill'}, 400
# Find the skill by ID
skill = Skill.query.get(data['id'])
if not skill:
return {'message': 'Skill not found'}, 404
# Delete the skill
try:
skill.delete()
return {'message': 'Skill deleted successfully'}, 200
except Exception as e:
return {'message': f'Error deleting skill: {e}'}, 500
For instance, in the delete
method, when a user clicks the delete button, the method obtains the skill ID, searches for it in the database, and deletes the entry. This ensures that the skill is completely removed from the system. The response is returned to the frontend, confirming the action with either a success or error message.
Call to Algorithm Request:
The backend logic is triggered by frontend events such as form submissions and button clicks. For example, when the “Add Skill” button is clicked, the form data is sent as a POST request to the API. The request body contains the skill details (name, level, user ID), and the backend creates the new entry in the database.
Similarly, when a user clicks the “Update” or “Delete” button for a specific skill, the frontend sends the corresponding PUT or DELETE request to the API. The backend processes these requests and updates the database accordingly. After each operation, a GET request is automatically triggered to refresh the frontend and display the latest data.
For all actions (POST, PUT, DELETE), error handling is implemented in both frontend and backend. In case of an issue, an appropriate message is displayed to the user, and the operation is canceled if necessary.
// Update a skill in the database and table (PUT)
async function updateRow(row, id) {
// Access the cells in the row that contain the skill name and skill level
const skillCell = row.cells[1];
const levelCell = row.cells[2];
// Prompt the user to input the new skill name and skill level, prefilled with current values
const newSkillName = prompt('Update Skill Name:', skillCell.textContent);
const newLevel = prompt('Update Level:', levelCell.textContent);
// Proceed if both values are provided (not null)
if (newSkillName !== null && newLevel !== null) {
// Create an updated skill object with the new values
const updatedSkill = {
id: id, // Unique ID of the skill being updated
skill_name: newSkillName.trim(), // Updated skill name (trimmed of extra spaces)
skill_level: newLevel.trim(), // Updated skill level (trimmed of extra spaces)
user_id: parseInt(id, 10), // User ID associated with the skill (converted to integer)
};
try {
// Send the updated skill data to the backend using a PUT request
const response = await fetch(`${API_URL}`, {
method: 'PUT', // Use PUT method to update data
headers: { 'Content-Type': 'application/json' }, // Specify that the body is JSON
body: JSON.stringify(updatedSkill), // Convert the updated skill object to a JSON string
});
// If the response is not OK (e.g., server error), throw an error
if (!response.ok) throw new Error('Failed to update skill.');
// If the update is successful, update the table row to reflect the changes
skillCell.textContent = updatedSkill.skill_name;
levelCell.textContent = updatedSkill.skill_level;
} catch (error) {
// Log any errors that occur during the PUT request
console.error('Error updating skill:', error);
}
}
}
Fetch Request
// Fetch skills from the database and display them in the table (GET)
async function fetchSkills() {
try {
const response = await fetch(API_URL, { method: 'GET' });
if (!response.ok) throw new Error('Failed to fetch skills.');
const skills = await response.json();
const table = document.getElementById('skillsTable');
table.innerHTML = ''; // Clear table before populating
skills.forEach(skill => addRowToTable(skill.id, skill.skill_name, skill.skill_level));
} catch (error) {
console.error('Error fetching skills:', error);
}
}
Aarush
FULL STACK BLOG - AARUSH
Executive Summary:
For this sprint, I developed a full-stack feature that stores, manages, and analyzes chess moves and their evaluations. I created a backend database to store chess moves, built a RESTful API for CRUD operations, and integrated the backend with a frontend interface. Users can interact with this feature on the analysis page, where they can view, add, update, and delete chess moves dynamically.
Team Purpose:
Our team is working on a chess-based social media platform where users can discuss chess strategies, analyze their games, and learn from others. My feature complements this vision by providing users with the tools to analyze their games, save key moves, and review them later for improvement and discussion.
Individual Feature:
My feature focuses on enabling users to store and manage their chess moves and evaluations. Users can upload PGN files to analyze moves and save these moves to a database for later use. The manager page lets users perform CRUD operations, providing full control over their stored data. This feature enhances the user experience by allowing in-depth review and customization of their chess gameplay data. Key functionalities include:
- POST: Adds a new evaluation to the database with the move number, move, and evaluation score.
def post(self): data = request.get_json() if not data or 'evaluation' not in data or 'move' not in data or 'move_number' not in data: return {'message': 'Move data is required.'}, 400 evaluation = Evaluation( evaluation=data.get('evaluation'), move=data.get('move'), move_number=data.get('move_number') ) try: evaluation.create() return jsonify(evaluation.read()), 201 except Exception as e: return {'message': f'Error saving evaluation: {e}'}, 500
- GET: Displays all saved moves in a dynamic table on the analysis page.
def get(self): all_evaluations = Evaluation.query.all() return jsonify([evaluation.read() for evaluation in all_evaluations])
- PUT: Updates the evaluation or status of a move (e.g., marking it as “played”).
def put(self): data = request.get_json() if not data or 'id' not in data: return {'message': 'ID is required to update the evaluation'}, 400 evaluation = Evaluation.query.get(data['id']) if not evaluation: return {'message': 'Evaluation not found'}, 404 try: evaluation.update(data) return jsonify(evaluation.read()) except Exception as e: return {'message': f'Error updating evaluation: {e}'}, 500
- DELETE: Removes moves from the database when no longer needed.
def delete(self): data = request.get_json() if not data or 'id' not in data: return {'message': 'ID is required for deletion'}, 400 evaluation = Evaluation.query.get(data['id']) if not evaluation: return {'message': 'Evaluation not found'}, 404 try: evaluation.delete() return {'message': 'Evaluation deleted successfully'}, 200 except Exception as e: return {'message': f'Error deleting evaluation: {e}'}, 500
Input/Output Requests:
The evaluation API supports all CRUD operations:
- GET: The analysis page fetches all moves from the backend and displays them in a table dynamically.
- POST: A POST request is triggered when a game is completed, saving new moves and their evaluations to the database.
- PUT: Users can update the status of a move, such as marking it as “played,” which sends a PUT request to the API.
- DELETE: A DELETE request is made when a user removes a move using the “Remove” button.
These requests have been tested thoroughly using Postman to ensure correct functionality. Error handling ensures proper HTTP status codes (e.g., 200 for success, 404 for not found) are returned for each request.
Requests & DOM Integration:
Fetch requests on the frontend dynamically interact with the REST API:
- GET Requests: Populate the DOM by fetching data from the backend and rendering it in a table. Loops are used to iterate through the response data, creating rows dynamically.
- POST Requests: Send new move data to the backend when a game concludes, and update the DOM to reflect the added move.
- PUT Requests: Allow users to edit move details, such as changing the evaluation or marking a move as “played.”
- DELETE Requests: Dynamically remove moves from the database and the DOM.
The backend uses Flask_SQLAlchemy as an ORM to manage database queries, abstracting SQL operations for ease of development.
Algorithmic Requests:
My
_CRUD
class in the backend API handles POST, PUT, and DELETE requests: - The POST method takes move data from the frontend, validates it, and adds it to the database. Duplicate checks ensure data integrity.
- The PUT method allows partial updates to entries, such as changing the evaluation or marking a move as “played.”
- The DELETE method removes a move from the database based on its unique ID, returning an appropriate success or error message.
Each method parses incoming request data, performs validation, interacts with the database, and returns a JSON response with a status code.
Call to Algorithm Requests:
Frontend JavaScript functions handle the integration of the REST API with the UI:
- A POST request is triggered when the user adds a move played in the game according to provided pgn file.
- GET requests populate the moves table on page load, displaying all saved moves.
- PUT requests allow users to mark moves as “played” or update their evaluations. These changes are reflected immediately in the DOM.
- DELETE requests remove moves from the database and dynamically update the table to reflect the changes.
For example, the “Mark as Played” button sends a PUT request to the API. Once the request is successful, the move is added to the “Played Moves” list and removed from the available moves table.
Summary:
This feature provided valuable experience in building and integrating a full-stack application:
- Backend: Developed a RESTful API with robust CRUD operations and integrated it with a database.
- Frontend: Designed a dynamic interface that allows users to manage their chess gameplay data effectively.
- Testing: Used Postman for testing API endpoints and ensured smooth interaction between the backend and frontend. This project helped me better understand the complexities of full-stack development, especially in managing state and ensuring seamless communication between the frontend and backend. I’m excited to refine and expand this feature in future sprints