- Overview of Website
- Individual Feature
- Input / Output Requests
- List Requests
- Algorithmic Code Request
- Call to Algorithm Request
Overview of Website
Cantella is a site that was created with the intention to give students various resources in which they could use in order to succeed in school. We have flashcards, quizzes, competitive leaderboards, a way to keep track of your grades and study sessions, and more.
Individual Feature
The individual feature in which I have been tasked to create was a program that allows a user to input their grade for any given subject and additional notes to describe why or for what assignment they received this grade for.
Input / Output Requests
The grade log feature can be accessed by the following path: /cantella_frontend/gradelog.
Here you can input data through the frontend which will subsequently be stored into the backend.
Frontend Demo
Frontend View:
Backend View:
Postman
In order to first retrieve raw data from Postman, we first have to send a POST request to localhost:8887/api/authenticate
and input the data:
{
"uid": "niko",
"password": "123niko"
}
Sending this request will output: Authentication for niko successful
.
This signifies that we are now logged into the site and hence have access to the grade log feature; a feature only accessible when a user is logged in.
Once logged in, one should now send a GET request to localhost:8887/api/gradelog
In the case of the example above, this would be the output:
[
{
"date": "2025-01-27 00:06:57",
"grade": 100.0,
"id": 1,
"notes": "Assignment was first Unit Test. It was really easy.",
"subject": "Math",
"user_id": 3
}
]
Database Management
Having the ability to clear, backup, and restore a database are useful, if not essential, for a functioning database to have. The following will demonstrate the usage of db_init (for clearing a database), db_backup (for backing up a database), and db_restore (for restoring a database).
db_backup (Backup)
To run db_backup, once must go to their backend repository and run the following in their VS Code terminal: scripts/db_backup.py
. This “command” is not so much a command as it is a path. When we enter the path to the db_backup file, the terminal assumes we want to run the file and so it does. Inputting this filepath outputs the following:
Data backed up to the backup directory.
Database backed up to instance/volumes/user_management_bak.db
This output is telling us that all the entries in our main database, user_management.db
, have been stored to the backup database, user_management_bak.db
. Now when we clear the database with db_init or sending DELETE requests from the frontend or postman, all our data will still be protected and stored in our backup database, regardless of the changes made to the main one.
db_init (Clear)
To run db_init, once must go to their backend repository and run the following in their VS Code terminal: scripts/db_init.py
. To reiterate from before, we are essentially typing in the path to db_init as it not invoke the running of the file. Inputting this filepath outputs the following:
Warning, you are about to lose all data in the database!
Do you want to continue? (y/n)
In our case, we type y, as we want to continue and clear all the data in the database.
Doing so outputs:
Database backed up to instance/volumes/user_management_bak.db
All tables dropped.
Generating data.
This output is telling us that all tables or, more pragmatically speaking, all our data in our databases, have been cleared. Additionally, new data is being generated, assuming you have default data that needs to be automatically added. When it comes to default data, there can be extra output depending if one has added such or not.
Now that we have backed up our data and cleared it from the main database, it’s time to restore it back.
db_restore (Restore)
Same song, different tune. Input scripts/db_restore.py
to the terminal and you will be outputted with:
Data restored to the new database.
The data that we backed up to user_management_bak.db
is now copied back into user_management.db
, allowing one to retrieve any data they may have deleted or lost.
List Requests
JSON to DOM (HTML)
In order to first convert JSON into DOM (aka into an HTML format), we first have to fetch that stored JSON data from the backend. The code below outlines how this is done:
// Filepath: /cantela_frontend/navigation/gradelog.md
// Function to load grade logs from the backend
async function loadGradeLogs() {
try {
// Make a GET request to the backend API to fetch grade logs
const response = await fetch('http://127.0.0.1:8887/api/gradelog', {
credentials: 'include', // Include credentials for authentication
});
// Check if the response is successful
if (response.ok) {
// Parse the JSON data from the response
const logs = await response.json();
// Call the function to display the grade logs, passing the JSON data
displayGradeLogs(logs);
} else {
// Log an error message if the response is not successful
console.error('Failed to load grade logs.');
}
} catch (error) {
// Log any errors that occur during the fetch operation
console.error('Error:', error);
}
}
The last thing that is done, given the response is successful, is passing that now retrieved JSON data in the next function, displayGradeLogs(logs);
// Filepath: /cantela_frontend/navigation/gradelog.md
// Function to display grade logs in the DOM
function displayGradeLogs(logs) {
// Clear the container that holds the grade logs
gradeLogContainer.innerHTML = '';
// Check if there are no logs to display
if (logs.length === 0) {
// Display a message indicating no grade logs were found
gradeLogContainer.innerHTML = '<p>No grade logs found.</p>';
return;
}
// Group logs by subject
const groupedLogs = logs.reduce((acc, log) => {
// If the subject is not already a key in the accumulator, add it
if (!acc[log.subject]) {
acc[log.subject] = [];
}
// Push the current log into the array for its subject
acc[log.subject].push(log);
return acc;
}, {});
// Iterate over each subject in the grouped logs
Object.keys(groupedLogs).forEach((subject) => {
// Create a container element for the subject group
const subjectElement = document.createElement('div');
subjectElement.className = 'subject-group'; // Add a class for styling
subjectElement.innerHTML = `<h3>${subject}</h3>`; // Add the subject title
let totalGrades = 0; // Initialize variables for calculating average grade
let gradeCount = 0;
// Iterate through the logs for the current subject
groupedLogs[subject].forEach((log) => {
// Create a container element for the individual grade log
const logElement = document.createElement('div');
logElement.className = 'grade-log'; // Add a class for styling
// Convert the JSON data into DOM elements with structured information
logElement.innerHTML = `
<p><strong>Grade:</strong> <span class="grade">${log.grade}</span></p>
<p>${log.notes}</p>
<p><small>${new Date(log.date).toLocaleString()}</small></p>
<button class="edit-log-btn" data-id="${log.id}">Edit</button>
<button class="delete-log-btn" data-id="${log.id}">Delete</button>
`;
// Append the log element to the subject group container
subjectElement.appendChild(logElement);
// Accumulate the total grades and count for calculating the average
totalGrades += parseFloat(log.grade);
gradeCount++;
});
There is of lot of fluff in this code, but the main thing that one should focus their attention is where that JSON data is now being converted to DOM elements.
Queries
In this snippet of code, we are defining how the API will retrieve the grade logs. This is done by using the third party SQL .query
method that allows us to essentially send a request to the database in order to extract the grade logs which are stored in a list. Once this is done, the list of the gradelog instances are converted to a list of dictionaries as to establish a key value pair for each of the respective categories such as grade and subject that is provided by Gradelog.
# Filepath: cantela_backend/api/gradelog.py
class GradelogAPI:
class _CRUD(Resource):
@token_required() # Authentication token
def get(self):
"""Get all grade entries for the current user."""
current_user = g.current_user
# Query the database to get all grade logs for the current user
all_gradelogs = GradeLog.query.filter_by(user_id=current_user.id).all()
# Convert the list of GradeLog instances to a list of dictionaries
gradelog = [log.read() for log in all_gradelogs]
return jsonify(gradelog)
As mentioned before The get method and query method are both functions by third party SQL libraries, making it relatively easier to interact with databases.
CRUD Methods
These methods provide us the most essential tools when manipulating a database. The ability to create, read, update, and delete the data to which is stored in such a database.
# Filepath: cantela_backend/model/gradelog.py
def create(self):
"""
Add the current GradeLog instance to the database.
Returns:
self: The created GradeLog instance if successful, None otherwise.
"""
try:
db.session.add(self)
db.session.commit()
return self
except IntegrityError:
db.session.rollback()
return None
def read(self):
"""
Convert the GradeLog instance to a dictionary.
Returns:
dict: A dictionary representation of the GradeLog instance.
"""
return {
"id": self.id,
"user_id": self.user_id,
"subject": self.subject,
"grade": self.grade,
"notes": self.notes,
"date": self.date.strftime('%Y-%m-%d %H:%M:%S')
}
def update(self, data):
"""
Update the GradeLog instance with new data.
Args:
data (dict): A dictionary containing the new data for the GradeLog instance.
Returns:
self: The updated GradeLog instance.
"""
for key, value in data.items():
if key == "subject":
self.subject = value
if key == "grade":
self.grade = value
if key == "notes":
self.notes = value
db.session.commit()
return self
def delete(self):
"""
Delete the current GradeLog instance from the database.
"""
db.session.delete(self)
db.session.commit()
Algorithmic Code Request
Methods: The methods in this case would be the various CRUD methods defined in the api file
Sequencing: The steps in this method are executed in a specific order:
- Retrieve the authenticated user.
- Query the database for grade logs.
- Initialize an empty list.
- Return a JSON response at the end.
Selection: The selection happens implicitly when filtering grade logs by user_id:
all_gradelogs = GradeLog.query.filter_by(user_id=current_user.id).all()
Iteration:
for log in all_gradelogs:
gradelog.append(log.read())
Jsonify: The list of grade log dictionaries are returned as JSON response
# Filepath: cantela_backend/api/gradelog.py
# Define the GradelogAPI class
class GradelogAPI:
# Define an inner class _CRUD that inherits from Resource
class _CRUD(Resource):
@token_required()
def post(self):
"""
Create a new grade entry.
This method handles POST requests to create a new grade log entry.
It requires the user to be authenticated and expects a JSON payload
with 'subject' and 'grade' fields. Optionally, 'notes' can also be provided.
"""
# Get the current authenticated user
current_user = g.current_user
# Get the JSON data from the request body
data = request.get_json()
# Validate input: Check if 'subject' and 'grade' are present in the data
if not data or 'subject' not in data or 'grade' not in data:
return {"message": "Subject and grade are required"}, 400
# Create a new GradeLog instance with the provided data
new_grade_log = GradeLog(
user_id=current_user.id,
subject=data['subject'],
grade=data['grade'],
notes=data.get('notes', '') # Use an empty string if 'notes' is not provided
)
# Save the new grade log to the database
created_log = new_grade_log.create()
if created_log:
# Return a success message with the ID of the created grade log
return {"message": "Grade logged successfully", "grade_log_id": created_log.id}, 201
# Return an error message if the grade log could not be created
return {"message": "Failed to create grade log"}, 500
@token_required()
def get(self):
"""
Get all grade entries for the current user.
This method handles GET requests to retrieve all grade log entries
for the authenticated user. It returns a JSON array of grade logs.
"""
# Get the current authenticated user (Sequencing)
current_user = g.current_user
# Query the database to get all grade logs for the current user (Sequencing)
all_gradelogs = GradeLog.query.filter_by(user_id=current_user.id).all()
# Initialize an empty list to hold the grade logs (Sequencing)
gradelog = []
# Iterate over the list of GradeLog instances (Iteration)
for log in all_gradelogs:
# Convert each GradeLog instance to a dictionary and append to the list (Iteration)
gradelog.append(log.read())
# Return the list of grade logs as a JSON response (Sequencing)
return jsonify(gradelog)
@token_required()
def put(self):
"""
Update an existing grade log.
This method handles PUT requests to update an existing grade log entry.
It requires the user to be authenticated and expects a JSON payload
with 'id' and the fields to be updated ('subject', 'grade', 'notes').
"""
# Get the JSON data from the request body
data = request.get_json()
# Validate input: Check if 'id' is present in the data
if not data or 'id' not in data:
return {"message": "Grade Log ID is required"}, 400
# Query the database to get the grade log by ID
grade_log = GradeLog.query.get(data['id'])
# Check if the grade log exists and belongs to the current user
if not grade_log or grade_log.user_id != g.current_user.id:
return {"message": "Grade Log not found or unauthorized"}, 404
# Update the grade log with the new data
updated_log = grade_log.update(data)
# Return the updated grade log as a JSON response
return updated_log.read(), 200
@token_required()
def delete(self):
"""
Delete a grade log.
This method handles DELETE requests to delete an existing grade log entry.
It requires the user to be authenticated and expects the 'id' of the grade log
to be provided as a query parameter.
"""
# Get the grade log ID from the query parameters
grade_log_id = request.args.get('id')
# Validate input: Check if the grade log ID is provided and is a digit
if not grade_log_id or not grade_log_id.isdigit():
return {"message": "A valid Grade Log ID is required"}, 400
# Query the database to get the grade log by ID
grade_log = GradeLog.query.get(int(grade_log_id))
# Check if the grade log exists and belongs to the current user
if not grade_log or grade_log.user_id != g.current_user.id:
return {"message": "Grade Log not found or unauthorized"}, 404
try:
# Delete the grade log from the database
grade_log.delete()
# Return a success message
return {"message": "Grade Log deleted successfully"}, 200
except Exception as e:
# Return an error message if an exception occurs
return {"message": f"An error occurred: {str(e)}"}, 500
# Register the _CRUD resource with the API
api.add_resource(GradelogAPI._CRUD, '/gradelog')
Call to Algorithm Request
// Filepath: /cantela_frontend/navigation/gradelog.md
try {
const response = await fetch('http://127.0.0.1:8887/api/gradelog', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
credentials: 'include',
});
if (response.ok) {
alert('Grade Log added successfully!');
gradeLogForm.reset();
loadGradeLogs(); // Refresh logs
} else {
const errorText = await response.text();
alert('Failed to add grade log: ' + errorText);
}
} catch (error) {
console.error('Error:', error);
alert('An error occurred while adding the grade log.');
}