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.