1a
COMPONENT A: PROGRAM CODE (CREATED INDEPENDENTLY OR COLLABORATIVELY)
Submit one PDF file that contains all of your program code (including comments). Include comments or acknowledgments for any part of the submitted program code that has been written by someone other than you and/or your collaborative partner(s).
In your program, you must include student-developed program code that contains the following:
Instructions for input from one of the following:
- the user (including user actions that trigger events)
- a device
- an online data stream
- a file Use of at least one list (or other collection type) to represent a collection of data that is stored and used to manage program complexity and help fulfill the program’s purpose
At least one procedure that contributes to the program’s intended purpose, where you have defined:
- the procedure’s name
- the return type (if necessary)
- one or more parameters An algorithm that includes sequencing, selection, and iteration that is in the body of the selected procedure
Calls to your student-developed procedure
Instructions for output (tactile, audible, visual, or textual) based on input and program functionality
On the backend
/api/user.py
I created _SearchCRUD function. Everything else was made by team members. My portion of the project was everything related to the search function.
import json, jwt
from flask import Blueprint, request, jsonify, current_app, Response
from flask_restful import Api, Resource # used for REST API building
from datetime import datetime
from auth_middleware import token_required
from model.users import User, Design
user_api = Blueprint('user_api', __name__,
url_prefix='/api/users')
# API docs https://flask-restful.readthedocs.io/en/latest/api.html
api = Api(user_api)
class UserAPI:
class _CRUD(Resource): # User API operation for Create, Read. THe Update, Delete methods need to be implemeented
def post(self): # Create method
''' Read data for json body '''
body = request.get_json()
''' Avoid garbage in, error checking '''
# validate name
name = body.get('name')
if name is None or len(name) < 2:
return {'message': f'Name is missing, or is less than 2 characters'}, 400
# validate uid
uid = body.get('uid')
if uid is None or len(uid) < 2:
return {'message': f'User ID is missing, or is less than 2 characters'}, 400
# look for password and dob
password = body.get('password')
dob = body.get('dob')
''' #1: Key code block, setup USER OBJECT '''
uo = User(name=name,
uid=uid,images="Thing")
''' Additional garbage error checking '''
# set password if provided
if password is not None:
uo.set_password(password)
# convert to date type
if dob is not None:
try:
uo.dob = datetime.strptime(dob, '%Y-%m-%d').date()
except:
return {'message': f'Date of birth format error {dob}, must be mm-dd-yyyy'}, 400
''' #2: Key Code block to add user to database '''
# create user in database
user = uo.create()
print(uo)
# success returns json of user
if user:
return jsonify(user.read())
# failure returns error
return {'message': f'Processed {name}, either a format error or User ID {uid} is duplicate'}, 400
@token_required
def get(self, current_user): # Read Method
print("get successful")
users = User.query.all() # read/extract all users from database
json_ready = [user.read() for user in users] # prepare output in json
return jsonify(json_ready) # jsonify creates Flask response object, more specific to APIs than json.dumps
@token_required
def put(self, current_user):
body = request.get_json() # get the body of the request
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid']
uid = body.get('uid')
name = body.get('name')
password = body.get('password')
users = User.query.all()
for user in users:
if user.uid == cur_user:
if uid == None:
uid = user.uid
if name == None:
name = user.name
if password == None:
password = user.password
user.update(name,uid,password)
return f"{user.read()} Updated"
@token_required
def delete(self, current_user):
# body = request.get_json()
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid']
users = User.query.all()
for user in users:
if user.uid==cur_user: # modified with the and user.id==cur_user so random users can't delete other ppl
user.delete()
return jsonify(user.read())
@token_required
def patch(self, current_user):
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid']
users = User.query.all()
for user in users:
if user.uid==cur_user:
thing = {
"id": user.id,
"name": user.name,
"uid": user.uid,
"type": user.type,
}
return jsonify(thing)
class _DesignCRUD(Resource): # Design CRUD
@token_required
def post(self, current_user): # Create design
''' Read data for json body '''
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid']
users = User.query.all()
for user in users:
if user.uid==cur_user: # modified with the and user.id==cur_user so random users can't delete other ppl
id = user.id
print("here")
body = request.get_json()
name = body.get('name')
content = body.get('content')
description = body.get('description')
type = body.get('type')
if (type != "public" and type != "private"):
return {'message': f'Design type must be public or private'}, 400
do = Design(id=id, type=type, content=content, name=name,description=description)
design = do.create()
# success returns json of user
if design:
return jsonify(user.read())
@token_required
def delete(self, current_user):
body = request.get_json() # get the body of the request
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid']
users = User.query.all()
for user in users:
if user.uid==cur_user: # modified with the and user.id==cur_user so random users can't delete other ppl
id = user.id
like = body.get('like')
dislike = body.get('dislike')
name = body.get('name')
if (like != "add") and (dislike != "add") and (like != "remove") and (dislike != "remove"):
return f"Like/Dislike must be add or remove", 400
designs = Design.query.all()
for design in designs:
if design.userID == id and design.name == name:
design.update('','','', (1 if like == "add" else (-1 if like == "remove" else 0)), (1 if dislike == "add" else (-1 if dislike == "remove" else 0)))
return f"{design.read()} Updated"
return f"Cannot locate design", 400
@token_required
def put(self, current_user):
body = request.get_json() # get the body of the request
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid']
users = User.query.all()
for user in users:
if user.uid==cur_user: # modified with the and user.id==cur_user so random users can't delete other ppl
id = user.id
name = body.get('name')
content = body.get('content')
type = body.get('type')
description = body.get('description')
designs = Design.query.all()
for design in designs:
if design.userID == id and design.name == name:
design.update('',content,type,0,0,description)
return f"{design.read()} Updated"
return f"Cannot locate design", 400
@token_required
def patch(self, current_user):
body = request.get_json() # get the body of the request
name = body.get('name')
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid']
users = User.query.all()
for user in users:
if user.uid==cur_user: # modified with the and user.id==cur_user so random users can't delete other ppl
id = user.id
designs = Design.query.all()
for design in designs:
if design.userID == id and design.name == name:
return jsonify(design.read())
return f"Cannot locate design", 400
class _SearchCRUD(Resource):
# public search of all designs
def get(self):
design_return=[]# all designs stored in the database
designs = Design.query.all()
for design in designs: # we going through every design
if(design.read()['Type']=='public'):
design_return.append(design.__repr__())
return jsonify({"Designs":design_return}) # returning designs of all users that are public
# get all private designs
@token_required
def put(self, current_user):
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid'] # current user
users = User.query.all()
for user in users:
if user.uid==cur_user:
id = user.id
designs=Design.query.all() # this is all the designs for the user
design_return=[]# all designs stored in the database for the user
for design in designs: # we going through every design
if design.userID == id:
design_return.append(design.__repr__())
return jsonify({"Designs":design_return}) # returning all the designs of the user
class Images(Resource):
@token_required
def post(self,current_user):
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid'] # current user
body=request.get_json()
base64=body.get("Image")
users = User.query.all()
for user in users:
if user.uid == cur_user:
user.updatepfp(base64)
print("succesful")
@token_required
def get(self,current_user):
print("here")
token = request.cookies.get("jwt")
cur_user = jwt.decode(token, current_app.config["SECRET_KEY"], algorithms=["HS256"])['_uid'] # current user
users = User.query.all()
for user in users:
if user.uid == cur_user:
# print(type(user))
# print(jsonify(user.getProfile()))
return jsonify(user.getprofile())
class _Security(Resource):
def post(self):
try:
body = request.get_json()
if not body:
return {
"message": "Please provide user details",
"data": None,
"error": "Bad request"
}, 400
''' Get Data '''
uid = body.get('uid')
if uid is None:
return {'message': f'User ID is missing'}, 400
password = body.get('password')
''' Find user '''
user = User.query.filter_by(_uid=uid).first()
if user is None or not user.is_password(password):
return {'message': f"Invalid user id or password"}, 400
if user:
try:
token = jwt.encode(
{"_uid": user._uid},
current_app.config["SECRET_KEY"],
algorithm="HS256"
)
resp = Response("Authentication for %s successful" % (user._uid))
resp.set_cookie("jwt", token,
max_age=3600,
secure=True,
httponly=False,
path='/',
samesite='None' # This is the key part for cross-site requests
# domain="frontend.com"
)
return resp
except Exception as e:
return {
"error": "Something went wrong",
"message": str(e)
}, 500
return {
"message": "Error fetching auth token!",
"data": None,
"error": "Unauthorized"
}, 404
except Exception as e:
return {
"message": "Something went wrong!",
"error": str(e),
"data": None
}, 500
# building RESTapi endpoint
api.add_resource(_CRUD, '/')
api.add_resource(_DesignCRUD, '/design')
api.add_resource(_SearchCRUD, '/search')
api.add_resource(Images,'/images')
api.add_resource(_Security, '/authenticate')
main.py
This file is essential to running the backend and sets up the Flask server on the localhost 8086 port. It was copied over and modified from the nighthawkcoders repository.
import threading
# import "packages" from flask
from flask import render_template,request # import render_template from "public" flask libraries
from flask.cli import AppGroup
# import "packages" from "this" project
from __init__ import app, db, cors # Definitions initialization
# setup APIs
from api.covid import covid_api # Blueprint import api definition
from api.joke import joke_api # Blueprint import api definition
from api.user import user_api # Blueprint import api definition
from api.player import player_api
# database migrations
from model.users import initUsers
from model.players import initPlayers
# setup App pages
from projects.projects import app_projects # Blueprint directory import projects definition
# Initialize the SQLAlchemy object to work with the Flask app instance
db.init_app(app)
# register URIs
app.register_blueprint(joke_api) # register api routes
app.register_blueprint(covid_api) # register api routes
app.register_blueprint(user_api) # register api routes
app.register_blueprint(player_api)
app.register_blueprint(app_projects) # register app pages
@app.errorhandler(404) # catch for URL not found
def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('404.html'), 404
@app.route('/') # connects default URL to index() function
def index():
return render_template("index.html")
@app.route('/table/') # connects /stub/ URL to stub() function
def table():
return render_template("table.html")
@app.before_request
def before_request():
# Check if the request came from a specific origin
allowed_origin = request.headers.get('Origin')
if allowed_origin in ['localhost:4200', 'http://127.0.0.1:4200', 'https://nighthawkcoders.github.io']:
cors._origins = allowed_origin
# Create an AppGroup for custom commands
custom_cli = AppGroup('custom', help='Custom commands')
# Define a command to generate data
@custom_cli.command('generate_data')
def generate_data():
initUsers()
initPlayers()
# Register the custom command group with the Flask application
app.cli.add_command(custom_cli)
# this runs the application on the development server
if __name__ == "__main__":
# change name for testing
app.run(debug=True, host="0.0.0.0", port="8086")
# server always runs on the address http://127.0.0.1:8086/
# http://127.0.0.1:8086/api/users/search
# http://127.0.0.1:8086/api/users/design
# http://127.0.0.1:8086/api/users/authenticate
On the Frontend
search.html
This is the search page html file, solely made by me.
<html lang="en">
<h1>Search for designs!</h1>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Search Bar with Toggle Buttons</title>
<style>
.toggle-buttons {
display: inline-block;
}
.toggle-buttons button {
background-color: #ccc;
border: none;
color: black;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 4px;
}
.toggle-buttons button.active {
background-color: #007bff;
color: white;
}
</style>
</head>
<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<body>
<form action="#" method="get" onsubmit="return checkButton()">
<input type="text" name="search" id="search" style="width: 400px;" placeholder="Enter your search term">
<button type="submit">Search</button>
<div class="toggle-buttons">
<button id="publicBtn" type="button" onclick="toggleButtons('publicBtn')">Public</button>
<button id="privateBtn" type="button" onclick="toggleButtons('privateBtn')">Private</button>
</div>
</form>
<div id="tableContainer"></div>
<script>
var ian;
function toggleButtons(activeButtonId) {
var buttons = document.querySelectorAll('.toggle-buttons button');
buttons.forEach(function(button) {
if (button.id === activeButtonId) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
}
function getSearchTerm() {
return document.getElementById('search').value.trim();
}
function checkButton() {
var publicBtn = document.getElementById('publicBtn');
var isPublicActive = publicBtn.classList.contains('active');
var searchTerm = document.getElementById('search').value.trim();
console.log(searchTerm);
if (isPublicActive) {
ian = "public";
getPublic();
} else {
ian = "private";
getPrivate();
}
}
function getPublic() {
fetch('http://127.0.0.1:8086/api/users/search')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data); // Handle the data returned from the server
displayDataInTable(data.Designs);
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
}
// function getAuthToken() {
// Retrieve the authentication token from cookies
// return document.cookie.replace(/(?:(?:^|.*;\s*)jwt\s*=\s*([^;]*).*$)|^.*$/, "$1");
//}
function getPrivate() {
// Making the GET request (public)
const authOptions = {
mode: 'cors', // no-cors, *cors, same-origin
credentials: 'include', // include, same-origin, omit
headers: {
'Content-Type': 'application/json',
},
method: 'PUT', // Override the method property
};
fetch('http://127.0.0.1:8086/api/users/search', authOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data); // Handle the data returned from the server
displayDataInTable(data.Designs);
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
}
function displayDataInTable(data) {
var tableContainer = document.getElementById('tableContainer');
var tableHTML = '<table id="table">';
const searchTerm = getSearchTerm();
tableHTML += '<tr><th>Name</th><th>Content</th><th>Description</th><th>Likes</th><th>Dislikes</th><th>Type</th></tr>';
data.forEach(function(item) {
// Check if searchTerm is a substring of Name or Content
if (searchTerm && (item.Name.includes(searchTerm) || (item.Content && item.Content.includes(searchTerm)))) {
tableHTML += '<tr>';
tableHTML += '<td class="nr">' + item.Name + '</td>';
tableHTML += '<td>' + (item.Content || '') + '</td>';
tableHTML += '<td>' + item.Description + '</td>';
tableHTML += '<td><button onclick="toggleLike(this, this.closest(`tr`).querySelector(`.nr`).textContent)" data-item-id="' + item.id + '">Like</button><span class="likesCount">' + item.Likes + '</span></td>';
tableHTML += '<td><button onclick="toggleDislike(this, this.closest(`tr`).querySelector(`.nr`).textContent)" data-item-id="' + item.id + '">Dislike</button><span class="dislikesCount">' + item.Dislikes + '</span></td>';
tableHTML += '<td>' + item.Type + '</td>';
tableHTML += '</tr>';
}
});
tableHTML += '</table>';
tableContainer.innerHTML = tableHTML;
}
document.getElementById('search').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
checkButton();
}
});
function toggleLike(button, name) {
console.log("toggle like")
var likesCount = button.parentNode.querySelector('.likesCount');
if (button.classList.contains('liked')) {
body = {
like: "remove",
name: name,
}
} else {
body = {
like: "add",
name: name,
}
}
const url ='http://127.0.0.1:8086/api/users/design';
const authOptions = {
mode: 'cors', // no-cors, *cors, same-origin
credentials: 'include', // include, same-origin, omit
headers: {
'Content-Type': 'application/json',
},
method: 'DELETE', // Override the method property
cache: 'no-cache', // Set the cache property
body: JSON.stringify(body)
};
fetch(url, authOptions)
.then(response => {
// handle error response from Web API
if (!response.ok) {
const errorMsg = 'Save error: ' + response.status;
console.log(errorMsg);
return;
} else {
window.location.href = "http://127.0.0.1:4100/CPT/search/?search=" + document.getElementById("search").value + "#"
}
})
// catch fetch errors (ie ACCESS to server blocked)
.catch(err => {
console.log(body);
console.error(err);
});
}
function toggleDislike(button, name) {
var dislikesCount = button.parentNode.querySelector('.dislikesCount');
if (button.classList.contains('disliked')) {
body = {
dislike: "remove",
name: name,
}
} else {
body = {
dislike: "add",
name: name,
}
}
const url ='http://127.0.0.1:8086/api/users/design';
const authOptions = {
mode: 'cors', // no-cors, *cors, same-origin
credentials: 'include', // include, same-origin, omit
headers: {
'Content-Type': 'application/json',
},
method: 'DELETE', // Override the method property
cache: 'no-cache', // Set the cache property
body: JSON.stringify(body)
};
fetch(url, authOptions)
.then(response => {
// handle error response from Web API
if (!response.ok) {
const errorMsg = 'Save error: ' + response.status;
console.log(errorMsg);
return;
} else {
window.location.href = "http://127.0.0.1:4100/CPT/search/?search=" + document.getElementById("search").value + ""
}
})
// catch fetch errors (ie ACCESS to server blocked)
.catch(err => {
console.log(body);
console.error(err);
});
}
</script>
1b
Describe one piece of documentation that would be appropriate to include with or in your program. Describe the intended purpose of this documentation by identifying who would use it and what they would do with it.
A piece of documentation that would be appropriate to include with the program would be a user’s manual/README for all programmers that might want to use or modify the program code. Since the project is rather complex, with multiple components, it would be vital to orient the programmer before they can begin modifying the program to their own purposes. The README would contain all the details on which file on the backend contains what functions and methods (for example the main.py runs the flask server with different links, /api/user.py contains all the CRUD definitions) and how the programmer can run the backend and frontend (using makefile and main.py).
1 part 2
COMPONENT C: PERSONALIZED PROJECT REFERENCE (CREATED INDEPENDENTLY)
You will create a Personalized Project Reference (PPR) similar to the PPR that will be used for the end of course exam written response questions. The PPR that you assemble and upload here will be helpful in responding to the following sample written response questions.
You will create a PDF document containing screen captures of the following code segments. Screen captures should not be blurry, and text should be at least 10-pointfont size. Your code segments should not include any comments.
NOTE: For the end of course exam administration, you will create your Personalized Project Reference in the AP Digital Portfolio.
Procedure: Capture and paste two program code segments you developed during the administration of this task that contain a student-developed procedure that implements an algorithm used in your program and a call to that procedure.
i. The first program code segment must be a student-developed procedure that:
- Defines the procedure’s name and return type (if necessary)
- Contains and uses one or more parameters that have an effect on the functionality of the procedure
- Implements an algorithm that includes sequencing, selection, and Iteration
ii. The second program code segment must show where your student-developed procedure is being called in your program.
List: Capture and paste two program code segments you developed during the administration of this task that contain a list (or other collection type) being used to manage complexity in your program.
i. The first program code segment must show how data have been stored in the list.
ii. The second program code segment must show the data in the same list being used, such as creating new data from the existing data or accessing multiple elements in the list, as part of fulfilling the program’s purpose.