diff --git a/.github/workflows/ms5yml b/.github/workflows/ms5yml
index d352c7f..6dcea33 100644
--- a/.github/workflows/ms5yml
+++ b/.github/workflows/ms5yml
@@ -1,59 +1,41 @@
-name: ms5API Testing
+name: API CI/CD
-on:
- push: #we want only the milstone5
- branches: [Milestone-5_feature_branch]
- pull_request:
- branches: [Milestone-5_feature_branch]
+on:
+ push:
+ branches:
+ - Milestone6-ericfeaturebranch
jobs:
- api-testing:
- runs-on: ubuntu-latest
+ tests:
+ runs-on: self-hosted
+ strategy:
+ matrix:
+ #python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+ python-version: ["3.8"]
steps:
- - name: Checkout code
- uses: actions/checkout@v2
-
- - name: Set up Python 3
- uses: actions/setup-python@v2
- with:
- python-version: '3.8'
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install flask flask_cors flasgger pylint pytest
-
- # Run pylint
- - name: Run pylint
- run: |
- pylint ./backend/*.py
- continue-on-error: false # Set to true to see warnings
-
- #docker image build
- - name: Build API Docker Image
- run: |
- docker build -t api-image -f Dockerfile .
-
- #run docker container
- - name: Start API Container
- run: |
- docker run -d -p 5000:5000 --name api-container api-image
-
- #running main.py
- - name: Start API Service
- run: |
- docker exec api-container python main.py
-
- #test_main.py
- - name: Run test_main.py
- run: |
- docker exec api-container pytest test_main.py --maxfail=1 --disable-warnings
-
- #cut the docker
- - name: Stop and Remove API Container
- run: |
- docker stop api-container
- docker rm api-container
-
-
+ - uses: actions/checkout@v3
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install pylint
+ run: |
+ python -m pip install --upgrade pip
+ pip install pylint
+
+ - name: Install pytest
+ run: |
+ python -m pip install --upgrade pip
+ pip install pytest
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+
+ - name: Run Api
+ run: |
+ python3 Backend/app.py &
+
+ - name: pytest
+ run: |
+ pytest
+
diff --git a/Backend/Dockerfile b/Backend/Dockerfile
deleted file mode 100644
index d085169..0000000
--- a/Backend/Dockerfile
+++ /dev/null
@@ -1,30 +0,0 @@
-# Use Python as the base image
-FROM python:3.8-slim-buster
-
-# Install pip and Python dependencies
-RUN apt-get update && apt-get install -y python3 python3-pip
-
-# Keeps Python from generating .pyc files in the container
-ENV PYTHONDONTWRITEBYTECODE=1
-
-# Turns off buffering for easier container logging
-ENV PYTHONUNBUFFERED=1
-
-
-# Set the working directory in the container
-WORKDIR /app
-
-
-# Install pip requirements
-COPY /requirements.txt .
-RUN python3 -m pip install -r requirements.txt
-
-# Copy main application file
-COPY generateData.py .
-
-# Set the port for the application
-ENV PORT=5000
-EXPOSE 5000
-
-# Command to run the application
-CMD ["python3", "generateData.py"]
\ No newline at end of file
diff --git a/Backend/main.py b/Backend/app.py
similarity index 59%
rename from Backend/main.py
rename to Backend/app.py
index 913cd46..a24b08b 100644
--- a/Backend/main.py
+++ b/Backend/app.py
@@ -2,11 +2,8 @@
This module provides functions for HTTP communication and
route functions
"""
-import sqlite3
-import databasefunctions
from flask import Flask, request, jsonify
-from petfuc import get_random_pet
-from userfuc import add_user_faviorte, remove_user_faviorte, replace_user_location
+from database_files import databasefunctions
app = Flask(__name__)
# -----------------------------------------------------------------------------------------
@@ -17,9 +14,11 @@ def add_newuser():
Adds new user to database
"""
data = request.get_json()
- newuser_id = databasefunctions.insert_user(data.get("name"), data.get("age"), data.get("location"))
+ newuser_id = databasefunctions.insert_user(data.get("username"),data.get("password"),
+ data.get("name"), data.get("age"),
+ data.get("location"))
- return jsonify({'user_id': newuser_id}), 200
+ return jsonify({'user_id': newuser_id}), 200
@app.route("/add_newpet", methods=["POST"])
def add_newpet():
@@ -28,21 +27,29 @@ def add_newpet():
"""
data = request.get_json()
newpet_id = databasefunctions.insert_pet(data.get("name"), data.get("age"),
- data.get("gender"), data.get("breed"), data.get("type"),
+ data.get("gender"), data.get("breed"), data.get("type"),
data.get("location"), data.get("photo_path"))
- return jsonify({'pet_id': newpet_id}), 200
+ return jsonify({'pet_id': newpet_id}), 200
@app.route("/add_newfavorite", methods=["POST"])
def add_newfavorite():
"""
Adds new user favorite to database
+ INSERT INTO pets (name, age, gender, breed, type, location, photo_path)
+ VALUES
+ ('Bella', 3, 'female', 'Golden Retriever', 'dog', 'New York', 'images/Chuck.jpg'),
+ ('Max', 5, 'male', 'Maine Coon', 'cat', 'Boston', 'images/gunner.jpg'),
+ ('Luna', 2, 'female', 'Siamese', 'cat', 'Chicago', 'images/luna.jpg'),
+ ('Charlie', 4, 'male', 'Beagle', 'dog', 'Los Angeles', 'images/lizzie_izzie.jpg'),
+ ('Daisy', 1, 'female', 'Labrador Retriever', 'dog', 'Miami', 'images/monster.jpg');
+
"""
data = request.get_json()
newfavorite_id = databasefunctions.insert_favorite(data.get("user_id"), data.get("pet_id"))
- return jsonify({'faviorte_id': newfavorite_id}), 200
+ return jsonify({'faviorte_id': newfavorite_id}), 200
@@ -66,7 +73,7 @@ def removepet():
Remove pet from database
"""
data = request.get_json()
- petid = data.get("petid")
+ petid = data.get("pet_id")
roweffected = databasefunctions.remove_pet(petid)
return (f"{roweffected}, row was deleted in pet table"), 200
@@ -85,6 +92,25 @@ def removefavorite():
# ---------------------------------------------------------------------
+
+@app.route("/fetch_userid", methods=["POST"])
+def fetch_userid():
+ """
+ fetch userid associated with username and password
+ """
+ data = request.get_json()
+ username = data.get("username")
+ password = data.get("password")
+
+
+ userid = databasefunctions.fetch_userid_from_username_and_password(username, password)
+
+ return jsonify(userid), 200
+
+
+
+
+
@app.route("/fetch_pet", methods=["POST"])
def fetch_pet():
"""
@@ -94,7 +120,7 @@ def fetch_pet():
petid = data.get("pet_id")
users_row = databasefunctions.fetch_pet_by_id(petid)
- return jsonify(users_row), 200
+ return jsonify(users_row), 200
@app.route("/fetch_user", methods=["POST"])
@@ -106,7 +132,7 @@ def fetch_user():
userid = data.get("user_id")
users_row = databasefunctions.fetch_user_by_id(userid)
- return jsonify(users_row), 200
+ return jsonify(users_row), 200
@app.route("/fetch_favorited_pets", methods=["POST"])
@@ -118,11 +144,35 @@ def fetch_favorite_pet():
userid = data.get("user_id")
favorited_pets = databasefunctions.fetch_favorites_by_user(userid)
- return jsonify(favorited_pets), 200
+ return jsonify(favorited_pets), 200
# ---------------------------------------------------------------
+@app.route("/fetch_allusers", methods=["POST"])
+def fetch_allusers():
+ """
+ fetches all users in database
+ """
+
+ allusers = databasefunctions.fetch_all_users()
+ return jsonify(allusers), 200
+
+
+@app.route("/fetch_allpets", methods=["POST"])
+def fetch_allpets():
+ """
+ fetches all pets in database
+ """
+
+ allpets = databasefunctions.fetch_all_pets()
+ return jsonify(allpets), 200
+
+
+
+
+
+
if __name__ == "__main__":
- app.run(host="0.0.0.0", port=5000, debug=True)
+ app.run(host="0.0.0.0", debug=True)
diff --git a/Backend/database_files/__pycache__/databasefunctions.cpython-310.pyc b/Backend/database_files/__pycache__/databasefunctions.cpython-310.pyc
new file mode 100644
index 0000000..d1bdb58
Binary files /dev/null and b/Backend/database_files/__pycache__/databasefunctions.cpython-310.pyc differ
diff --git a/Backend/databasefunctions.py b/Backend/database_files/databasefunctions.py
similarity index 79%
rename from Backend/databasefunctions.py
rename to Backend/database_files/databasefunctions.py
index f0cecf4..4aa835e 100644
--- a/Backend/databasefunctions.py
+++ b/Backend/database_files/databasefunctions.py
@@ -1,20 +1,21 @@
+'''DATABASE FUNCTIONS'''
from contextlib import closing
import sqlite3
def connect_db():
"""Establish a connection to the SQLite database."""
- return sqlite3.connect('Projectdatabase.db') #this might change
+ return sqlite3.connect('projectdatabase.db') #this might change
-# ---------------------------------------------------------------------------------------------- insert functions
+# --------------------- insert functions
-def insert_user(name, age, location):
+def insert_user(username, password, name, age, location):
"""Insert a new user into the accounts table."""
with closing(connect_db()) as conn, conn:
cursor = conn.cursor()
cursor.execute(
- '''INSERT INTO accounts (name, age, location)
- VALUES (?, ?, ?)''',
- (name, age, location)
+ '''INSERT INTO accounts (username, password, name, age, location)
+ VALUES (?, ?, ?, ?, ?)''',
+ (username, password, name, age, location)
)
conn.commit()
return cursor.lastrowid
@@ -45,7 +46,7 @@ def insert_favorite(user_id, pet_id):
conn.commit()
return cursor.lastrowid
-# ------------------------------------------------------------------------------------- remove functions
+# ---------------------------remove functions
def remove_user(user_id):
"""Remove a pet from the accounts table."""
@@ -77,79 +78,99 @@ def remove_favorite(user_id, pet_id):
"""Remove a favorite row from the favorites table based on user_id and pet_id."""
with closing(connect_db()) as conn:
cursor = conn.cursor()
-
- # Execute DELETE query to remove the row matching user_id and pet_id
+
+ # Execute DELETE query to remove the row matching user_id and pet_id
cursor.execute("""
DELETE FROM favorites
WHERE user_id = ? AND pet_id = ?
""", (user_id, pet_id))
-
+
conn.commit()
return cursor.rowcount # Returns the number of rows affected (should be 1 if successful)
-# ----------------------------------------------------------------------------------------------------------- change fucntions
+# ------------------------------------------- change fucntions
def change_user_attributes(user_id, attribute, change):
"""Update a user's attribute in the accounts table."""
-
+
# List of valid column names to avoid SQL injection
- valid_columns = ['name', 'age', 'location'] # Add your valid column names here
-
+ valid_columns = ['name', 'age', 'location', 'password'] # Add your valid column names here
+
if attribute not in valid_columns:
raise ValueError("Invalid attribute name.")
-
+
with closing(connect_db()) as conn:
cursor = conn.cursor()
-
+
# Use the attribute directly, now it's safe since we validated it
cursor.execute(f"""
UPDATE accounts
SET {attribute} = ?
WHERE user_id = ?
""", (change, user_id))
-
+
conn.commit()
return fetch_user_by_id(user_id)
def change_pet_attributes(pet_id, attribute, change):
"""Update a user's attribute in the accounts table."""
-
+
# List of valid column names to avoid SQL injection
- valid_columns = ['name', 'age', 'location', 'breed', 'gender', 'type', 'photo_path'] # Add your valid column names here
-
+ valid_columns = ['name', 'age', 'location', 'breed',
+ 'gender', 'type', 'photo_path'] # Add your valid column names here
+
if attribute not in valid_columns:
raise ValueError("Invalid attribute name.")
-
+
with closing(connect_db()) as conn:
cursor = conn.cursor()
-
+
# Use the attribute directly, now it's safe since we validated it
cursor.execute(f"""
UPDATE pets
SET {attribute} = ?
WHERE pet_id = ?
""", (change, pet_id))
-
+
conn.commit()
return fetch_pet_by_id(pet_id)
-# ----------------------------------------------------------------------------------------------------------- fetch functions
+# ------------------------------------- fetch functions
+
+
+def fetch_userid_from_username_and_password(username,password):
+ """Get a users ID from there username and password"""
+
+ with closing(connect_db()) as conn:
+ cursor = conn.cursor()
+
+ # Execute DELETE query to remove the row matching user_id and pet_id
+ cursor.execute("""
+ SELECT user_id FROM accounts
+ WHERE username = ? AND password = ?
+ """, (username, password))
+
+ result = cursor.fetchone()
+ if result:
+ return result[0]
+ else:
+ return None
def fetch_favorites_by_user(user_id):
"""Fetch all favorite pets for a given user_id."""
with closing(connect_db()) as conn:
cursor = conn.cursor()
-
+
cursor.execute("""
SELECT pets.*
FROM pets
INNER JOIN favorites ON pets.pet_id = favorites.pet_id
WHERE favorites.user_id = ?
""", (user_id,))
-
+
return cursor.fetchall()
diff --git a/Backend/databaseinit.py b/Backend/database_files/initdatabase.py
similarity index 52%
rename from Backend/databaseinit.py
rename to Backend/database_files/initdatabase.py
index 861c9a8..e90b8d4 100644
--- a/Backend/databaseinit.py
+++ b/Backend/database_files/initdatabase.py
@@ -1,20 +1,20 @@
-'''ONLY USE THIS IF THE DATABASE IS EMPTY. IT'S THE INIT CODE'''
+'''INIT TABLES IN DATABASE'''
import sqlite3
-from contextlib import closing
-import os
-def populate_test_data(db_path):
+def create_database_tabels(db_path):
'''THE ACTUAL FUNCITON'''
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
- conn.execute("PRAGMA foreign_keys = ON")
+ conn.execute("PRAGMA foreign_keys = ON")
# Create tables
cursor.executescript('''
CREATE TABLE IF NOT EXISTS accounts (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL,
+ password TEXT NOT NULL,
name TEXT NOT NULL,
age INTEGER,
location TEXT,
@@ -41,35 +41,21 @@ def populate_test_data(db_path):
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
- ''')
- # Insert sample users
- cursor.executescript('''
- INSERT INTO accounts (name, age) VALUES
- ('Ben Ten', 28),
- ('Spongebob Smith', 35),
- ('Dipper Pines', 25),
- ('Emilia Kirej', 20);
- ''')
+ INSERT INTO pets (name, age, gender, breed, type, location, photo_path)
+ VALUES
+ ('Bella', 3, 'female', 'Golden Retriever', 'dog', 'New York', 'images/Chuck.jpg'),
+ ('Max', 5, 'male', 'Maine Coon', 'cat', 'Boston', 'images/gunner.jpg'),
+ ('Luna', 2, 'female', 'Siamese', 'cat', 'Chicago', 'images/luna.jpg'),
+ ('Charlie', 4, 'male', 'Beagle', 'dog', 'Los Angeles', 'images/lizzie_izzie.jpg'),
+ ('Daisy', 1, 'female', 'Labrador Retriever', 'dog', 'Miami', 'images/monster.jpg');
- # Insert sample pets
- cursor.executescript('''
- INSERT INTO pets (name, age, gender, breed, type, location, photo_path) VALUES
- ('Gunner', 9, 'male', 'Golden Retriever', 'dog', 'Myrtle Beach, SC', 'images/gunner.jpg'),
- ('Lizzie Izzie', 5, 'female', 'Donskoy', 'cat', 'Milford, CT', 'images/lizzie_izzie.jpg'),
- ('Chuck', 1, 'male', 'Corgi', 'dog', 'Orlando, FL', 'images/chuck.jpg'),
- ('Monster', 5, 'female', 'Maine Coon', 'cat', 'Houston, TX', 'images/monster.jpg');
- ''')
- # Insert sample favorites
- cursor.executescript('''
- INSERT INTO favorites (user_id, pet_id) VALUES
- (2, 4),
- (1, 2);
''')
+
conn.commit()
conn.close()
if __name__ == "__main__":
- populate_test_data('Projectdatabase.db')
+ create_database_tabels('projectdatabase.db')
diff --git a/Backend/docker_files/Dockerfile b/Backend/docker_files/Dockerfile
new file mode 100644
index 0000000..305c2f8
--- /dev/null
+++ b/Backend/docker_files/Dockerfile
@@ -0,0 +1,18 @@
+FROM python:3.9-slim
+WORKDIR /Docker1
+
+
+COPY /database_files/databasefunctions.py /Docker1/database_files/databasefunctions.py
+COPY app.py app.py
+COPY /database_files/initdatabase.py /Docker1/database_files/initdatabase.py
+
+
+# Copy requirements.txt from docker_files directory into the container
+COPY docker_files/requirements.txt /Docker1/requirements.txt
+
+# Install Python dependencies inside Docker
+RUN pip3 install -r /Docker1/requirements.txt
+
+EXPOSE 5000
+
+CMD python3 /Docker1/database_files/initdatabase.py && python3 app.py
diff --git a/Backend/docker_files/requirements.txt b/Backend/docker_files/requirements.txt
new file mode 100644
index 0000000..2e64847
--- /dev/null
+++ b/Backend/docker_files/requirements.txt
@@ -0,0 +1,4 @@
+Flask==3.0.3
+requests==2.26.0
+pytest==7.2.1
+flask_cors==3.0.10
diff --git a/Backend/requirements.txt b/Backend/requirements.txt
deleted file mode 100644
index 5eeda4f..0000000
--- a/Backend/requirements.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-Flask==2.0.3
-requests==2.26.0
-pandas==1.3.4
-pytest==7.2.1
-flask_cors==3.0.10
-#Werkzeug==2.1.3
-#pip freeze
-
diff --git a/Backend/testData.db b/Backend/testData.db
deleted file mode 100644
index 14a1ced..0000000
Binary files a/Backend/testData.db and /dev/null differ
diff --git a/Backend/test_app.py b/Backend/test_app.py
new file mode 100644
index 0000000..da5e088
--- /dev/null
+++ b/Backend/test_app.py
@@ -0,0 +1,133 @@
+"""
+Pytest for fake API
+"""
+import requests
+
+UNIQUE_USER_ID = None
+UNIQUE_PET_ID = None
+
+
+def test_insert_user():
+ """
+ Test for http://localhost:5000/add_newuser, adding user to database
+ """
+ global UNIQUE_USER_ID
+ url = "http://localhost:5000"
+
+ UNIQUE_USER_ID = requests.post((url + "/add_newuser"),
+ json={"username":"sam2213",
+ "password":"catlover20","name": "sam",
+ "age": 17, "location": "jamaica"},timeout=10)
+
+ response1 = requests.post((url + "/fetch_user"),json=UNIQUE_USER_ID.json(), timeout=10)
+ response2 = requests.post((url + "/fetch_allusers"), timeout=10)
+
+ assert (response1.json() in response2.json()) is True
+
+
+def test_insert_pet():
+ """
+ Test for http://localhost:5000/add_newpet, adding new pet to database to database
+ """
+ global UNIQUE_PET_ID
+ url = "http://localhost:5000"
+ UNIQUE_PET_ID = requests.post((url + "/add_newpet"),
+ json={"name": "sam", "age": 17,
+ "gender":"female","breed":"pug",
+ "type":"cat","location": "jamaica",
+ "photo_path":"images/sam.jpg"},timeout=10)
+
+ response1 = requests.post((url + "/fetch_pet"),json=UNIQUE_PET_ID.json(), timeout=10)
+ response2 = requests.post((url + "/fetch_allpets"), timeout=10)
+
+ assert (response1.json() in response2.json()) is True
+
+def test_insert_favorite_relation():
+ """
+ Test for http://localhost:5000/add_newfavorite, adding users favorited pet into the database
+ """
+ global UNIQUE_USER_ID
+ global UNIQUE_PET_ID
+
+ url = "http://localhost:5000"
+
+
+ requests.post((url + "/add_newfavorite"),
+ json={"user_id": UNIQUE_USER_ID.json().get("user_id"),
+ "pet_id": UNIQUE_PET_ID.json().get("pet_id")},timeout=10)
+
+ response1 = requests.post((url + "/fetch_pet"),json=UNIQUE_PET_ID.json(), timeout=10)
+ response2 = requests.post((url + "/fetch_favorited_pets"),json=UNIQUE_USER_ID.json(),timeout=10)
+
+ assert (response1.json() in response2.json()) is True
+
+
+# -----------------------------------------------------------------------------
+
+def test_fetch_userid():
+ """
+ Test for http://localhost:5000/fetch_userid,
+ retrieve userid accosiated with username and password
+ """
+
+ global UNIQUE_USER_ID
+ global UNIQUE_PET_ID
+
+ url = "http://localhost:5000"
+ userid = requests.post((url + "/fetch_userid"),
+ json={"username": "sam2213",
+ "password": "catlover20"}, timeout=10)
+
+ assert userid.json() == UNIQUE_USER_ID.json().get("user_id")
+
+# ----------------------------------------------------------------------------------
+
+def test_remove_favorite_relation():
+ """
+ Test for http://localhost:5000/remove_favorite, remove users favorited pet from the database
+ """
+
+ global UNIQUE_USER_ID
+ global UNIQUE_PET_ID
+
+ url = "http://localhost:5000"
+
+ requests.delete((url + "/remove_favorite"),
+ json={"user_id": UNIQUE_USER_ID.json().get("user_id"),
+ "pet_id": UNIQUE_PET_ID.json().get("pet_id")},timeout=10)
+
+
+ response = requests.post((url + "/fetch_favorited_pets"),json=UNIQUE_USER_ID.json(),timeout=10)
+
+ assert (response.json()) == []
+
+
+def test_remove_user():
+ """
+ Test for http://localhost:5000/remove_user
+ """
+
+ global UNIQUE_USER_ID
+ global UNIQUE_PET_ID
+
+ url = "http://localhost:5000"
+
+ requests.delete((url + "/remove_user"), json=UNIQUE_USER_ID.json(), timeout=10)
+
+ response = requests.post((url + "/fetch_user"),json=UNIQUE_USER_ID.json(), timeout=10)
+
+ assert response.json() is None
+
+
+def test_remove_pet():
+ """
+ Test for http://localhost:5000/remove_pet
+ """
+
+ url = "http://localhost:5000"
+
+ requests.delete((url + "/remove_pet"), json=UNIQUE_PET_ID.json(), timeout=10)
+
+ response = requests.post((url + "/fetch_pet"),json=UNIQUE_PET_ID.json(), timeout=10)
+
+ assert response.json() is None
diff --git a/README.md b/README.md
index 7a7c605..112d09b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,15 @@
-# cse2102-fall24-Team51
-Eric Asante eoa21004
-Kyle Kirejczyk kek20009
-Ishayu Ray isr21011
-Kunal bagga kub21001
-Link to Trello: https://trello.com/b/j8t0Ulvk/group-51-jira-kaban-board
-Link to Figma Prototype: https://www.figma.com/design/mKMWW1sIOVpuQKOlapBINQ/ishayu.ray
\ No newline at end of file
+# CSE2102-fall24-Team51
+
+Eric Asante eoa21004
+Kyle Kirejczyk kek20009
+Ishayu Ray isr21011
+Kunal Bagga kub21001
+
+Link to Trello: [https://trello.com/b/j8t0Ulvk/group-51-jira-kaban-board](https://trello.com/b/j8t0Ulvk/group-51-jira-kaban-board)
+Link to Figma Prototype: [https://www.figma.com/design/mKMWW1sIOVpuQKOlapBINQ/ishayu.ray](https://www.figma.com/design/mKMWW1sIOVpuQKOlapBINQ/ishayu.ray)
+
+# Terminal commands to run DOCKERIZED API:
+Make sure you cd into Backend folder.
+1. docker build -f docker_files/Dockerfile -t team51-backend .
+2. docker run -d -p 5000:5000 team51-backend
+3. pytest