diff --git a/.DS_Store b/.DS_Store index 953c41e94..bbd9324c2 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..a4b6a55f5 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,36 @@ +# Use the official Python 3.10 slim image +FROM python:3.10-slim + +# Set the working directory inside the container +WORKDIR /app + +# Install system dependencies including build tools and MariaDB development files +RUN apt-get update && \ + apt-get install -y \ + build-essential \ + pkg-config \ + python3-dev \ + libmariadb-dev-compat \ + libmariadb-dev \ + && rm -rf /var/lib/apt/lists/* + +# Upgrade pip (optional but recommended) +RUN pip install --upgrade pip + +# Copy the requirements.txt file from the correct relative path into the container +COPY Sprint_1/AI_Fitness_Project/requirements.txt /app/requirements.txt + +# Install Python dependencies from requirements.txt +RUN pip install --no-cache-dir -r /app/requirements.txt + +# Copy the rest of your application code into the container +COPY Sprint_1/AI_Fitness_Project /app + +# Set environment variables (using the recommended syntax) +ENV PYTHONUNBUFFERED=1 + +# Expose the port your app uses (adjust if needed) +EXPOSE 5000 + +# Run the application +CMD ["python", "/app/app.py"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..3f7427c42 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,10 @@ +{ + "name": "AI Fitness Dev Container", + "build": { + "dockerfile": "Dockerfile" + }, + "workspaceFolder": "/workspace", + "mounts": ["source=${localWorkspaceFolder},target=/workspace,type=bind"], + "remoteUser": "vscode" + } + \ No newline at end of file diff --git a/README.md b/README.md index b2588bc51..4993f3e8d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -My groupmembers are: -- XXXX -- XXXX -- XXXX -- XXXX +The groupmembers are: +- Sebastian Alcibar +- Ryan Maazous +- Thomas Marshall +- Jonah Minkoff +- Brady Miller ------------------ Fill in some information about your project under this ------------------ +## Our group has decided to work on the Mental Health Support App. diff --git a/Sprint 1/.DS_Store b/Sprint 1/.DS_Store new file mode 100644 index 000000000..23cfab5c7 Binary files /dev/null and b/Sprint 1/.DS_Store differ diff --git a/Sprint 1/AI Fitness Project/.DS_Store b/Sprint 1/AI Fitness Project/.DS_Store new file mode 100644 index 000000000..1e90259a5 Binary files /dev/null and b/Sprint 1/AI Fitness Project/.DS_Store differ diff --git a/Sprint 1/AI Fitness Project/.gitignore b/Sprint 1/AI Fitness Project/.gitignore new file mode 100644 index 000000000..cd6eb6e16 --- /dev/null +++ b/Sprint 1/AI Fitness Project/.gitignore @@ -0,0 +1,4 @@ +.env +database.db +__pycache__/ +venv/ \ No newline at end of file diff --git a/Sprint 1/AI Fitness Project/app.py b/Sprint 1/AI Fitness Project/app.py new file mode 100644 index 000000000..ee287da0f --- /dev/null +++ b/Sprint 1/AI Fitness Project/app.py @@ -0,0 +1,112 @@ +from flask import Flask, render_template, session +import os +from backend.db import db +from backend.tables import User +from flask_login import LoginManager, login_required, login_user, logout_user, current_user +from flask import Flask, render_template, redirect, url_for, flash +from flask_bcrypt import Bcrypt +from backend.forms import RegisterForm, LoginForm, SurveyForm, FitnessLogWorkoutForm, FitnessLogCardioForm +from backend.tables import User +import secrets + + +app = Flask(__name__) +bcrypt = Bcrypt(app) + +# Handles our SQLite database +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db' +app.config['SECRET_KEY'] = secrets.token_hex(16) +db.init_app(app) + +# Handles login verification +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view = "login" +@login_manager.user_loader +def load_user(user_id): + return User.query.get(int(user_id)) + + +# Renders home page +@app.route('/', methods=['GET', 'POST']) +@login_required +def home(): + fitness_goals= current_user.fitness_goals + + if fitness_goals: + fitness_goals_list = fitness_goals.split(",") + else: + fitness_goals_list = None + return render_template('index.html', first_name=current_user.first_name, fitness_goals=fitness_goals_list) + + +# Renders fitness log page +@app.route('/fitness-log', methods=['GET', 'POST']) +def fitness_log(): + workout_form = FitnessLogWorkoutForm() + cardio_form = FitnessLogCardioForm() + return render_template('fitness-log.html', workout_form=workout_form, cardio_form=cardio_form) + +# Renders about page +@app.route('/about') +def about(): + return render_template('about.html') + +# Renders main AI chat page +@app.route('/main-chat') +def main_chat(): + return render_template('main-chat.html') + +# Renders login page +@app.route('/login', methods=['GET', 'POST']) +def login(): + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(username=form.username.data).first() + if user: + if bcrypt.check_password_hash(user.password, form.password.data): + login_user(user) + return redirect(url_for('home')) + return render_template('login.html', form=form) + +# Handles logout functionality +@app.route('/logout', methods=['GET', 'POST']) +@login_required +def logout(): + logout_user() + return redirect(url_for('login')) + +# Renders register page +@app.route('/register', methods=['GET', 'POST']) +def register(): + form = RegisterForm() + + + if form.validate_on_submit(): + hashed_password = bcrypt.generate_password_hash(form.password.data) + new_user = User(username=form.username.data, password=hashed_password, first_name=form.first_name.data, last_name=form.last_name.data) + db.session.add(new_user) + db.session.commit() + login_user(new_user) + return redirect(url_for('survey')) + + return render_template('register.html', form=form) + +@app.route('/survey', methods=['GET', 'POST']) +def survey(): + form = SurveyForm() + + if form.validate_on_submit(): + selected_goals = ",".join(form.fitness_goals.data) + current_user.fitness_goals = selected_goals + db.session.commit() + return redirect(url_for('home')) + + return render_template('survey.html', form=form) + +with app.app_context(): + db.create_all() + +if __name__ == "__main__": + + app.run(debug=True, host="0.0.0.0", port=5000) diff --git a/Sprint 1/AI Fitness Project/backend/db.py b/Sprint 1/AI Fitness Project/backend/db.py new file mode 100644 index 000000000..97a5faf13 --- /dev/null +++ b/Sprint 1/AI Fitness Project/backend/db.py @@ -0,0 +1,3 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() \ No newline at end of file diff --git a/Sprint 1/AI Fitness Project/backend/forms.py b/Sprint 1/AI Fitness Project/backend/forms.py new file mode 100644 index 000000000..eb6fe2c2f --- /dev/null +++ b/Sprint 1/AI Fitness Project/backend/forms.py @@ -0,0 +1,70 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, SubmitField, SelectMultipleField, widgets, IntegerField, FieldList, FormField, DecimalField +from wtforms.validators import InputRequired, Length, ValidationError, DataRequired, NumberRange +from backend.tables import User + +# Sets up the form on the register page +class RegisterForm(FlaskForm): + username = StringField(validators=[InputRequired(), Length(min=4, max=20)], render_kw={"placeholder": "Username"}) + password = PasswordField(validators=[InputRequired(), Length(min=4, max=20)], render_kw={"placeholder": "Password"}) + first_name = StringField(validators=[InputRequired(), Length(min=1)], render_kw={"placeholder": "First Name"}) + last_name = StringField(validators=[InputRequired(), Length(min=1)], render_kw={"placeholder": "Last Name"}) + + submit = SubmitField("Register") + + def validate_username(self, username): + existing_user = User.query.filter_by( + username=username.data).first() + if existing_user: + raise ValidationError( + "That username already exists." + ) + + +# Sets up form on login page +class LoginForm(FlaskForm): + username = StringField(validators=[InputRequired(), Length(min=4, max=20)], render_kw={"placeholder": "Username"}) + + password = PasswordField(validators=[InputRequired(), Length(min=4, max=20)], render_kw={"placeholder": "Password"}) + + submit = SubmitField("Login") + + +class SurveyForm(FlaskForm): + fitness_goals = SelectMultipleField( + "What are your fitness goals?", + choices=[ + ("Build Muscle", "Build Muscle"), + ("Lose Weight", "Lose Weight"), + ("Increase Strength", "Increase Strength"), + ("Improve Cardio", "Improve Cardio") + ], + option_widget=widgets.CheckboxInput(), + widget=widgets.ListWidget(prefix_label=False) + ) + + submit = SubmitField("Submit") + + +class FitnessLogWorkoutForm(FlaskForm): + legs_reps = IntegerField('Total Reps', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + legs_sets = IntegerField('Total Sets', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + + chest_reps = IntegerField('Total Reps', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + chest_sets = IntegerField('Total Sets', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + + back_reps = IntegerField('Total Reps', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + back_sets = IntegerField('Total Sets', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + + bicep_reps = IntegerField('Total Reps', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + bicep_sets = IntegerField('Total Sets', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + + tricep_reps = IntegerField('Total Reps', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + tricep_sets = IntegerField('Total Sets', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + + submit = SubmitField("Submit") + +class FitnessLogCardioForm(FlaskForm): + miles = DecimalField('Total Miles', default=0, validators=[NumberRange(min=0, message="Value must be 0 or greater")]) + + submit = SubmitField("Submit") \ No newline at end of file diff --git a/Sprint 1/AI Fitness Project/backend/tables.py b/Sprint 1/AI Fitness Project/backend/tables.py new file mode 100644 index 000000000..429ba7f9c --- /dev/null +++ b/Sprint 1/AI Fitness Project/backend/tables.py @@ -0,0 +1,15 @@ +from backend.db import db +from flask_login import UserMixin + +# Creates our Sqlite table that holds user login/register info. +class User(db.Model, UserMixin): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(20), nullable=False, unique=True) + password = db.Column(db.String(80), nullable=False) + first_name = db.Column(db.String(20), nullable=False) + last_name = db.Column(db.String(20), nullable=False) + fitness_goals = db.Column(db.String(255), nullable=True) + + + + diff --git a/Sprint 1/AI Fitness Project/login_test.py b/Sprint 1/AI Fitness Project/login_test.py new file mode 100644 index 000000000..3e25affcc --- /dev/null +++ b/Sprint 1/AI Fitness Project/login_test.py @@ -0,0 +1,41 @@ +import requests +from bs4 import BeautifulSoup + + +base_url = "http://localhost:5000" +login_url = f"{base_url}/login" +home_url = f"{base_url}/home" + +test_username = 'test' +test_password = 'test' + +session = requests.Session() + +login_page = session.get(login_url) + +soup = BeautifulSoup(login_page.text, 'html.parser') + +csrf_token = soup.find('input', {'name': 'csrf_token'})['value'] + +login_data = { + 'csrf_token': csrf_token, + 'username': test_username, + 'password': test_password, + 'submit': 'Login' +} + +login_response = session.post(login_url, data=login_data) + +print("\nLogin Response Status Code:", login_response.status_code) + +if login_response.status_code == 200: + if "Welcome" in login_response.text: + print("Login test passed.") + elif "Invalid username or password" in login_response.text: + print("Login failed. Invalid username or password.") + else: + print("Login failed with status code 200 but no error message found.") +else: + print("Login failed. Status code:", login_response.status_code) + print("Response Content:") + print(login_response.text) diff --git a/Sprint 1/AI Fitness Project/requirements_mac.txt b/Sprint 1/AI Fitness Project/requirements_mac.txt new file mode 100644 index 000000000..0c9419795 --- /dev/null +++ b/Sprint 1/AI Fitness Project/requirements_mac.txt @@ -0,0 +1,51 @@ +asttokens==3.0.0 +bcrypt==4.3.0 +blinker==1.9.0 +click==8.1.8 +colorama==0.4.6 +comm==0.2.2 +debugpy==1.8.13 +decorator==5.2.1 +dnspython==2.7.0 +email_validator==2.2.0 +executing==2.2.0 +Flask==3.1.0 +Flask-Bcrypt==1.0.1 +Flask-Login==0.6.3 +Flask-MySQLdb==2.0.0 +Flask-SQLAlchemy==3.1.1 +Flask-WTF==1.2.2 +greenlet==3.1.1 +idna==3.10 +ipykernel==6.29.5 +ipython==8.10.0 +ipython_pygments_lexers==1.1.1 +itsdangerous==2.2.0 +jedi==0.19.2 +Jinja2==3.1.6 +jupyter_client==8.6.3 +jupyter_core==5.7.2 +MarkupSafe==3.0.2 +matplotlib-inline==0.1.7 +PyMySQL==1.0.2 #mysqlclient==2.2.7 +nest-asyncio==1.6.0 +packaging==24.2 +parso==0.8.4 +platformdirs==4.3.7 +prompt_toolkit==3.0.50 +psutil==7.0.0 +pure_eval==0.2.3 +Pygments==2.19.1 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +pywin32==310; sys_platform == "win32" +pyzmq==26.3.0 +six==1.17.0 +SQLAlchemy==2.0.39 +stack-data==0.6.3 +tornado==6.4.2 +traitlets==5.14.3 +typing_extensions==4.12.2 +wcwidth==0.2.13 +Werkzeug==3.1.3 +WTForms==3.2.1 diff --git a/Sprint 1/AI Fitness Project/requirements_windows.txt b/Sprint 1/AI Fitness Project/requirements_windows.txt new file mode 100644 index 000000000..2b694b2f8 --- /dev/null +++ b/Sprint 1/AI Fitness Project/requirements_windows.txt @@ -0,0 +1,51 @@ +asttokens==3.0.0 +bcrypt==4.3.0 +blinker==1.9.0 +click==8.1.8 +colorama==0.4.6 +comm==0.2.2 +debugpy==1.8.13 +decorator==5.2.1 +dnspython==2.7.0 +email_validator==2.2.0 +executing==2.2.0 +Flask==3.1.0 +Flask-Bcrypt==1.0.1 +Flask-Login==0.6.3 +Flask-MySQLdb==2.0.0 +Flask-SQLAlchemy==3.1.1 +Flask-WTF==1.2.2 +greenlet==3.1.1 +idna==3.10 +ipykernel==6.29.5 +ipython==9.0.2 +ipython_pygments_lexers==1.1.1 +itsdangerous==2.2.0 +jedi==0.19.2 +Jinja2==3.1.6 +jupyter_client==8.6.3 +jupyter_core==5.7.2 +MarkupSafe==3.0.2 +matplotlib-inline==0.1.7 +mysqlclient==2.2.7 +nest-asyncio==1.6.0 +packaging==24.2 +parso==0.8.4 +platformdirs==4.3.7 +prompt_toolkit==3.0.50 +psutil==7.0.0 +pure_eval==0.2.3 +Pygments==2.19.1 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +pywin32==310 +pyzmq==26.3.0 +six==1.17.0 +SQLAlchemy==2.0.39 +stack-data==0.6.3 +tornado==6.4.2 +traitlets==5.14.3 +typing_extensions==4.12.2 +wcwidth==0.2.13 +Werkzeug==3.1.3 +WTForms==3.2.1 diff --git a/Sprint 1/AI Fitness Project/static/.DS_Store b/Sprint 1/AI Fitness Project/static/.DS_Store new file mode 100644 index 000000000..130e3f076 Binary files /dev/null and b/Sprint 1/AI Fitness Project/static/.DS_Store differ diff --git a/Sprint 1/AI Fitness Project/static/scripts/fitness-log.js b/Sprint 1/AI Fitness Project/static/scripts/fitness-log.js new file mode 100644 index 000000000..1b949e0dd --- /dev/null +++ b/Sprint 1/AI Fitness Project/static/scripts/fitness-log.js @@ -0,0 +1,9 @@ +function toggleWorkoutForm() { + const form = document.getElementById('workout-form'); + form.style.display = (form.style.display === 'none' || form.style.display === '') ? 'block' : 'none'; +} + +function toggleCardioForm() { + const form = document.getElementById('cardio-form'); + form.style.display = (form.style.display === 'none' || form.style.display === '') ? 'block' : 'none'; +} \ No newline at end of file diff --git a/Sprint 1/AI Fitness Project/static/styles/fitness-log.css b/Sprint 1/AI Fitness Project/static/styles/fitness-log.css new file mode 100644 index 000000000..6621768a8 --- /dev/null +++ b/Sprint 1/AI Fitness Project/static/styles/fitness-log.css @@ -0,0 +1,26 @@ +.forms-wrapper { + display: flex; + gap: 20px; + justify-content: space-between; +} + +.workout-form, .cardio-form { + width: 48%; + padding-left: 10px; +} + +#workout-form, #cardio-form { + display: none; +} + +button { + margin-bottom: 15px; +} + +label { + margin-left: 10px; +} + +#submit-button { + padding-top: 20px; +} diff --git a/Sprint 1/AI Fitness Project/static/styles/navbar.css b/Sprint 1/AI Fitness Project/static/styles/navbar.css new file mode 100644 index 000000000..22a5a8cbf --- /dev/null +++ b/Sprint 1/AI Fitness Project/static/styles/navbar.css @@ -0,0 +1,65 @@ +html, body{ + margin: 0; + padding: 0; +} +#navbar { + * { + text-decoration: none; + padding: 0; + margin: 0; + box-sizing: border-box; + } + .navbar { + background: gray; + font-family: calibri; + padding: 10px 20px; + } + .navdiv { + display: flex; + align-items: center; + justify-content: space-between; + } + .logo a { + color: white; + font-size: 21px; + font-weight: bold; + } + ul { + display: flex; + list-style: none; + padding: 0; + } + li { + margin-right: 38px; + } + li a { + color: white; + font-size: 25px; + font-weight: bold; + } + li a:hover{ + color: lightgray; + } + .buttons { + display: flex; + gap: 10px; + } + button { + background-color: black; + border: none; + border-radius: 10px; + padding: 10px; + width: 90px; + cursor: pointer; + } + button a { + color: white; + font-weight: bold; + font-size: 15px; + display: block; + text-align: center; + } + button a:hover{ + color:lightgray; + } +} diff --git a/Sprint 1/AI Fitness Project/templates/about.html b/Sprint 1/AI Fitness Project/templates/about.html new file mode 100644 index 000000000..6f6039ed1 --- /dev/null +++ b/Sprint 1/AI Fitness Project/templates/about.html @@ -0,0 +1,12 @@ + + +
+ + +