diff --git a/BackEndFlask/.env b/BackEndFlask/.env index 70fad1b9e..47023316d 100644 --- a/BackEndFlask/.env +++ b/BackEndFlask/.env @@ -4,3 +4,7 @@ DEMO_ADMIN_PASSWORD=demo_admin DEMO_TA_INSTRUCTOR_PASSWORD=demo_ta DEMO_STUDENT_PASSWORD=demo_student SECRET_KEY=Thisissupposedtobesecret! +MYSQL_HOST=localhost +MYSQL_USER=skillbuilder +MYSQL_PASSWORD=WasPogil1# +MYSQL_DATABASE=account diff --git a/BackEndFlask/core/__init__.py b/BackEndFlask/core/__init__.py index 0b62c26c5..d70f6939a 100644 --- a/BackEndFlask/core/__init__.py +++ b/BackEndFlask/core/__init__.py @@ -2,11 +2,13 @@ from flask_marshmallow import Marshmallow from flask_sqlalchemy import SQLAlchemy from models.tests import testing +from dotenv import load_dotenv from flask import Flask from flask_cors import CORS +import subprocess +load_dotenv() import sys import os -import subprocess import re import redis @@ -78,13 +80,19 @@ def setup_cron_jobs(): # Initialize JWT jwt = JWTManager(app) +account_db_path = os.getcwd() + os.path.join(os.path.sep, "core") + os.path.join(os.path.sep, "account.db") + +MYSQL_HOST=os.getenv('MYSQL_HOST') + +MYSQL_USER=os.getenv('MYSQL_USER') + +MYSQL_PASSWORD=os.getenv('MYSQL_PASSWORD') + +MYSQL_DATABASE=os.getenv('MYSQL_DATABASE') + +db_uri = (f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}/{MYSQL_DATABASE}") -# Database configuration -account_db_path = os.path.join(os.getcwd(), "core", "account.db") -if os.path.exists(account_db_path): - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./account.db' -else: - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../instance/account.db' +app.config['SQLALCHEMY_DATABASE_URI'] = db_uri db = SQLAlchemy(app) ma = Marshmallow(app) diff --git a/BackEndFlask/dbcreate.py b/BackEndFlask/dbcreate.py index 8b7e47880..ebed9fc9e 100644 --- a/BackEndFlask/dbcreate.py +++ b/BackEndFlask/dbcreate.py @@ -35,9 +35,21 @@ except Exception as e: print(f"[dbcreate] an error ({e}) occured with db.create_all()") print("[dbcreate] exiting...") - os.abort() + raise e print("[dbcreate] successfully created new db") time.sleep(sleep_time) + if (get_roles().__len__() == 0): + print("[dbcreate] attempting to load existing roles...") + time.sleep(sleep_time) + load_existing_roles() + print("[dbcreate] successfully loaded existing roles") + time.sleep(sleep_time) + if(get_users().__len__()==0): + print("[dbcreate] attempting to load SuperAdminUser...") + time.sleep(sleep_time) + load_SuperAdminUser() + print("[dbcreate] successfully loaded SuperAdminUser") + time.sleep(sleep_time) if(get_rubrics().__len__()==0): print("[dbcreate] attempting to load existing rubrics...") time.sleep(sleep_time) @@ -61,18 +73,6 @@ time.sleep(sleep_time) load_existing_suggestions() print("[dbcreate] successfully loaded existing suggestions") - if(get_roles().__len__()==0): - print("[dbcreate] attempting to load existing roles...") - time.sleep(sleep_time) - load_existing_roles() - print("[dbcreate] successfully loaded existing roles") - time.sleep(sleep_time) - if(get_users().__len__()==0): - print("[dbcreate] attempting to load SuperAdminUser...") - time.sleep(sleep_time) - load_SuperAdminUser() - print("[dbcreate] successfully loaded SuperAdminUser") - time.sleep(sleep_time) if len(sys.argv) == 2 and sys.argv[1]=="demo": if(get_users().__len__()==1): print("[dbcreate] attempting to load demo Admin...") @@ -130,17 +130,17 @@ load_demo_admin_assessment_task() print("[dbcreate] successfully loaded demo AssessmentTask") time.sleep(sleep_time) - if(get_feedback().__len__()==0): - print("[dbcreate] attempting to load demo Feedback...") - time.sleep(sleep_time) - load_demo_feedback() - print("[dbcreate] successfully loaded demo Feedback") - time.sleep(sleep_time) if (get_completed_assessments().__len__() == 0): print("[dbcreate] attempting to load demo completed assessments...") time.sleep(sleep_time) load_demo_completed_assessment() print("[dbcreate] successfully loaded demo completed assessments") time.sleep(sleep_time) + if(get_feedback().__len__()==0): + print("[dbcreate] attempting to load demo Feedback...") + time.sleep(sleep_time) + load_demo_feedback() + print("[dbcreate] successfully loaded demo Feedback") + time.sleep(sleep_time) - print("[dbcreate] exiting...") \ No newline at end of file + print("[dbcreate] exiting...") diff --git a/BackEndFlask/models/schemas.py b/BackEndFlask/models/schemas.py index 3fbc3570d..433602e6d 100644 --- a/BackEndFlask/models/schemas.py +++ b/BackEndFlask/models/schemas.py @@ -16,29 +16,29 @@ Team(team_id, team_name, course_id, observer_id, date_created, active_until) TeamUser(team_user_id, team_id, user_id) AssessmentTask(assessment_task_id, assessment_task_name, course_id, rubric_id, role_id, due_date, time_zone, show_suggestions, show_ratings, unit_of_assessment, comment, number_of_teams) - Completed_Assessment(completed_assessment_id, assessment_task_id, by_role, team_id, user_id, initial_time, last_update, rating_observable_characteristics_suggestions_data) + Checkin(checkin_id, assessment_task_id, team_number, user_id, time) + CompletedAssessment(completed_assessment_id, assessment_task_id, by_role, team_id, user_id, initial_time, last_update, rating_observable_characteristics_suggestions_data) + Feedback(feedback_id, user_id, completed_assessment_id, feedback_time, lag_time) Blacklist(id, token) """ class Role(db.Model): __tablename__ = "Role" - __table_args__ = {'sqlite_autoincrement': True} - role_id = db.Column(db.Integer, primary_key=True) - role_name = db.Column(db.String(100), nullable=False) + role_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + role_name = db.Column(db.Text, nullable=False) class User(db.Model): __tablename__ = "User" - __table_args__ = {'sqlite_autoincrement': True} - user_id = db.Column(db.Integer, primary_key=True) - first_name = db.Column(db.String(30), nullable=False) - last_name = db.Column(db.String(30), nullable=False) - email = db.Column(db.String(255), unique=True, nullable=False) - password = db.Column(db.String(80), nullable=False) + user_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + first_name = db.Column(db.Text, nullable=False) + last_name = db.Column(db.Text, nullable=False) + email = db.Column(db.String(254), unique=True, nullable=False) + password = db.Column(db.Text, nullable=False) lms_id = db.Column(db.Integer, nullable=True) consent = db.Column(db.Boolean, nullable=True) owner_id = db.Column(db.Integer, ForeignKey(user_id), nullable=True) has_set_password = db.Column(db.Boolean, nullable=False) - reset_code = db.Column(db.String(6), nullable=True) + reset_code = db.Column(db.Text, nullable=True) is_admin = db.Column(db.Boolean, nullable=False) class Rubric(db.Model): @@ -46,14 +46,14 @@ class Rubric(db.Model): __table_args__ = {'sqlite_autoincrement': True} rubric_id = db.Column(db.Integer, primary_key=True) rubric_name = db.Column(db.String(100)) - rubric_description = db.Column(db.String(100), nullable=True) + rubric_description = db.Column(db.Text, nullable=True) owner = db.Column(db.Integer, ForeignKey(User.user_id), nullable=True) class Category(db.Model): __tablename__ = "Category" __table_args__ = {'sqlite_autoincrement': True} category_id = db.Column(db.Integer, primary_key=True) - category_name = db.Column(db.String(30), nullable=False) + category_name = db.Column(db.Text, nullable=False) description = db.Column(db.String(255), nullable=False) rating_json = db.Column(db.JSON, nullable=False) @@ -80,12 +80,11 @@ class SuggestionsForImprovement(db.Model): class Course(db.Model): __tablename__ = "Course" - __table_args__ = {'sqlite_autoincrement': True} - course_id = db.Column(db.Integer, primary_key=True) - course_number = db.Column(db.String(10), nullable=False) - course_name = db.Column(db.String(50), nullable=False) + course_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + course_number = db.Column(db.Text, nullable=False) + course_name = db.Column(db.Text, nullable=False) year = db.Column(db.Integer, nullable=False) - term = db.Column(db.String(50), nullable=False) + term = db.Column(db.Text, nullable=False) active = db.Column(db.Boolean, nullable=False) admin_id = db.Column(db.Integer, ForeignKey(User.user_id, ondelete='RESTRICT'), nullable=False) use_tas = db.Column(db.Boolean, nullable=False) @@ -102,9 +101,8 @@ class UserCourse(db.Model): class Team(db.Model): # keeps track of default teams for a fixed team scenario __tablename__ = "Team" - __table_args__ = {'sqlite_autoincrement': True} - team_id = db.Column(db.Integer, primary_key=True) - team_name = db.Column(db.String(25), nullable=False) + team_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + team_name = db.Column(db.Text, nullable=False) course_id = db.Column(db.Integer, ForeignKey(Course.course_id), nullable=False) observer_id = db.Column(db.Integer, ForeignKey(User.user_id, ondelete='RESTRICT'), nullable=False) date_created = db.Column(db.Date, nullable=False) @@ -112,34 +110,31 @@ class Team(db.Model): # keeps track of default teams for a fixed team scenario class TeamUser(db.Model): __tablename__ = "TeamUser" - __table_args__ = {'sqlite_autoincrement': True} - team_user_id = db.Column(db.Integer, primary_key=True) + team_user_id = db.Column(db.Integer, primary_key=True, autoincrement=True) team_id = db.Column(db.Integer, ForeignKey(Team.team_id), nullable=False) user_id = db.Column(db.Integer, ForeignKey(User.user_id), nullable=False) class AssessmentTask(db.Model): __tablename__ = "AssessmentTask" - __table_args__ = {'sqlite_autoincrement' : True} - assessment_task_id = db.Column(db.Integer, primary_key=True) - assessment_task_name = db.Column(db.String(100)) + assessment_task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + assessment_task_name = db.Column(db.Text) course_id = db.Column(db.Integer, ForeignKey(Course.course_id)) rubric_id = db.Column(db.Integer, ForeignKey(Rubric.rubric_id)) # how to handle updates and deletes role_id = db.Column(db.Integer, ForeignKey(Role.role_id)) due_date = db.Column(db.DateTime, nullable=False) - time_zone = db.Column(db.String(3), nullable=False) + time_zone = db.Column(db.Text, nullable=False) show_suggestions = db.Column(db.Boolean, nullable=False) show_ratings = db.Column(db.Boolean, nullable=False) unit_of_assessment = db.Column(db.Boolean, nullable=False) # true if team, false if individuals - comment = db.Column(db.String(3000), nullable=True) - create_team_password = db.Column(db.String(25), nullable=True) + comment = db.Column(db.Text, nullable=True) + create_team_password = db.Column(db.Text, nullable=True) number_of_teams = db.Column(db.Integer, nullable=True) max_team_size = db.Column(db.Integer, nullable=True) notification_sent = db.Column(DateTime(timezone=True), nullable=True) class Checkin(db.Model): # keeps students checking to take a specific AT __tablename__ = "Checkin" - __table_args__ = {'sqlite_autoincrement': True} - checkin_id = db.Column(db.Integer, primary_key=True) + checkin_id = db.Column(db.Integer, primary_key=True, autoincrement=True) assessment_task_id = db.Column(db.Integer, ForeignKey(AssessmentTask.assessment_task_id), nullable=False) # not a foreign key because in the scenario without fixed teams, there will not be default team entries # to reference. if they are default teams, team_number will equal the team_id of the corresponding team @@ -149,8 +144,7 @@ class Checkin(db.Model): # keeps students checking to take a specific AT class CompletedAssessment(db.Model): __tablename__ = "CompletedAssessment" - __table_args__ = {'sqlite_autoincrement': True} - completed_assessment_id = db.Column(db.Integer, primary_key=True) + completed_assessment_id = db.Column(db.Integer, primary_key=True, autoincrement=True) assessment_task_id = db.Column(db.Integer, ForeignKey(AssessmentTask.assessment_task_id)) completed_by = db.Column(db.Integer, ForeignKey(User.user_id), nullable=False) team_id = db.Column(db.Integer, ForeignKey(Team.team_id), nullable=True) @@ -162,8 +156,7 @@ class CompletedAssessment(db.Model): class Feedback(db.Model): __tablename__ = "Feedback" - __table_args__ = {'sqlite_autoincrement': True} - feedback_id = db.Column(db.Integer, primary_key=True) + feedback_id = db.Column(db.Integer, primary_key=True, autoincrement=True) user_id = db.Column(db.Integer, ForeignKey(User.user_id), nullable=False) completed_assessment_id = db.Column(db.Integer, ForeignKey(CompletedAssessment.completed_assessment_id), nullable=False) feedback_time = db.Column(DateTime(timezone=True), nullable=True) # time the student viewed their feedback \ No newline at end of file diff --git a/BackEndFlask/models/user.py b/BackEndFlask/models/user.py index b9ac1e235..f3011ec6a 100644 --- a/BackEndFlask/models/user.py +++ b/BackEndFlask/models/user.py @@ -242,7 +242,7 @@ def load_SuperAdminUser(): "password": str(os.environ.get('SUPER_ADMIN_PASSWORD')), "lms_id": 0, "consent": None, - "owner_id": 0, + "owner_id": None, "role_id": None }) diff --git a/BackEndFlask/requirements.txt b/BackEndFlask/requirements.txt index 653d710fd..423cf8d5b 100644 --- a/BackEndFlask/requirements.txt +++ b/BackEndFlask/requirements.txt @@ -15,4 +15,5 @@ Werkzeug >= 2.0.2 redis >= 4.5.5 python-dotenv >= 1.0.0 yagmail >= 0.15.293 -openpyxl >= 3.1.2 \ No newline at end of file +openpyxl >= 3.1.2 +cryptography >= 43.0.1 \ No newline at end of file diff --git a/BackEndFlask/run.py b/BackEndFlask/run.py index f1bba48e6..58643d83c 100644 --- a/BackEndFlask/run.py +++ b/BackEndFlask/run.py @@ -1,12 +1,4 @@ from core import app -# this variable is expected by the wsgi server -# application = app if __name__ == '__main__': - #The app.run(debug = True) line is needed if we are working on our local machine - # app.run(debug=True) - - #the app.run(host="0.0.0.0") line is currently commented out and if and only when we are seting up an EC2 instance app.run(host="0.0.0.0") - - # token: MFFt4RjpXNMh1c_T1AQj diff --git a/BackEndFlask/setupEnv.py b/BackEndFlask/setupEnv.py index 9b01a1b30..0effeb690 100755 --- a/BackEndFlask/setupEnv.py +++ b/BackEndFlask/setupEnv.py @@ -41,6 +41,8 @@ def cmd(command, parent_function): err(f"Error running command: {command} in function: {parent_function}") + err(f"Exception: {e}") + err(f"Return code: {e.returncode}") err(f"Output: {e.output}") diff --git a/Dockerfile.backend b/Dockerfile.backend index c43103ed1..386c23bc2 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -24,11 +24,8 @@ RUN pip install --no-cache-dir --upgrade pip \ # Copy the rest of the backend code COPY BackEndFlask/ /app/ -# Initialize the Backend environment -RUN python setupEnv.py -d - # Expose the Backend port EXPOSE 5000 # Start the Flask server -CMD ["python", "setupEnv.py", "-s"] +CMD ["python", "setupEnv.py", "-ds"] diff --git a/README.md b/README.md index d46b018d8..acdfa0f07 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ for analysis. - Python 3.12 and up. +- MySQL-Server. + - Homebrew 4.2.18 and up. - Redis 7.2.4 and up. @@ -117,6 +119,49 @@ NOTE: - WINDOWS DEVELOPERS ARE NO LONGER SUPPORTED. +## Setting up the MySQL Environment: ## + +- Run the following command to install MySQL-Server +on Linux: + + sudo apt install mysql-server + +- Run the following command to install MySQL-Server +on MacOS: + + brew install mysql + +- Run the following command to start MySQL-Server +on MacOS: + + brew services start mysql + +- Run the following command to start MySQL-Server +in a new terminal: + + sudo mysql -u root + +- Next use these commands to create an account +named skillbuilder and set the password to +"WasPogil1#" + + CREATE DATABASE account; + CREATE USER 'skillbuilder'@'localhost' IDENTIFIED BY 'WasPogil1#'; + GRANT ALL PRIVILEGES ON *.* TO 'skillbuilder'@'localhost'; + FLUSH PRIVILEGES; + exit; + +NOTE: + +- The password should be changed for deployment. + +- Once this is done, you can use: `setupEnv.py` as normal +to create the database. If for any reason you want to +access the database directly, run the following command: + + mysql -u skillbuilder -p + +and then type the password. ## Installing requirements ## diff --git a/compose.yml b/compose.yml index e33bf294c..548cf4c07 100644 --- a/compose.yml +++ b/compose.yml @@ -6,7 +6,10 @@ services: ports: - "127.0.0.1:5050:5000" depends_on: - - redis + redis: + condition: service_started + mysql: + condition: service_healthy volumes: # Mount the source files inside the container to allow for Flask hot reloading to work - "./BackEndFlask/Functions:/app/Functions:rw" @@ -18,6 +21,10 @@ services: environment: - REDIS_HOST=redis - FLASK_DEBUG=1 + - MYSQL_HOST=mysql + - MYSQL_USER=skillbuilder + - MYSQL_PASSWORD=WasPogil1# + - MYSQL_DATABASE=account redis: image: redis:7.2.4 @@ -44,9 +51,30 @@ services: networks: - app-network + mysql: + image: mysql:8.0 + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: account + MYSQL_USER: skillbuilder + MYSQL_PASSWORD: WasPogil1# + ports: + - "5551:3306" + volumes: + - mysql-data:/var/lib/mysql + networks: + - app-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + networks: app-network: driver: bridge volumes: + mysql-data: frontend-cache: