diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7148b367d2da835dedfe208b5aec5590b27bc048 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +FROM python:3.11.4 + +#Copy requirements +COPY requirements.txt /user_ms/ + +COPY app /user_ms/ + +#Set working direcroty +WORKDIR /user_ms + +#Install dependencies +RUN pip install --no-cache-dir -r requirements.txt + + +#Install other libraries in container +RUN apt-get update && apt-get install -y \ + unixodbc \ + unixodbc-dev \ + iputils-ping\ + apt-transport-https \ + gcc\ + curl \ + gnupg \ + freetds-dev \ + tdsodbc \ + && rm -rf /var/lib/apt/lists/* + + +#Install ODBC Driver for Microsoft SQL Server Section... +#Source: https://learn.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-ver16&tabs=debian18-install%2Calpine17-install%2Cdebian8-install%2Credhat7-13-install%2Crhel7-offline + + +#Add Microsoft repository GPG key +RUN curl https://packages.microsoft.com/keys/microsoft.asc | tee /etc/apt/trusted.gpg.d/microsoft.asc + +#Add the Microsoft SQL Server repository for Debian 12 +RUN curl https://packages.microsoft.com/config/debian/12/prod.list | tee /etc/apt/sources.list.d/mssql-release.list + + +#Add Microsoft GPG key +RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg + +#Add the Microsoft SQL Server repository for Debian 12 +RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/debian/12/prod bookworm main" > /etc/apt/sources.list.d/mssql-release.list + +#Update package list +RUN apt-get update + +#Install ODBC Driver 17 for SQL Server +RUN ACCEPT_EULA=Y apt-get install -y msodbcsql17 + +#Install mssql-tools +RUN ACCEPT_EULA=Y apt-get install -y mssql-tools +RUN bash -c "echo 'export PATH=\"$PATH:/opt/mssql-tools/bin\"' >> ~/.bashrc && source ~/.bashrc" + + +#RUN echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc && source ~/.bashrc + +#Install unixodbc-dev and kerberos library +RUN apt-get install -y libgssapi-krb5-2 + +#Set environment variables for ODBC configuration +ENV ODBCINI=/etc/odbc.ini +ENV ODBCSYSINI=/etc + +CMD ["python", "index.py"] \ No newline at end of file diff --git a/Dockerfile.userdb b/Dockerfile.userdb new file mode 100644 index 0000000000000000000000000000000000000000..c7d3ab70d82a45909512cf4bc2a09936ade8c4ad --- /dev/null +++ b/Dockerfile.userdb @@ -0,0 +1,27 @@ +FROM mcr.microsoft.com/mssql/server:2022-latest + +# Set environment variables +ENV SA_PASSWORD WebTechGroup3 +ENV ACCEPT_EULA=Y + +COPY User_Management.bak /var/opt/mssql/data/ + + + + +# Change user permissions +USER root + +RUN chown -R mssql:mssql /var/opt/mssql/data + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh + + +# Expose the default SQL Server port +EXPOSE 1433 + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] + + +# Define the default command to run when the container starts +CMD ["/opt/mssql/bin/sqlservr"] \ No newline at end of file diff --git a/Dockerfile.userms b/Dockerfile.userms new file mode 100644 index 0000000000000000000000000000000000000000..3e73e5fab859fa6eaf160356db11b952ca22c20a --- /dev/null +++ b/Dockerfile.userms @@ -0,0 +1,62 @@ +FROM python:3.11.4 + +#Copy requirements +COPY requirements.txt /user_ms/ + +COPY app /user_ms/ + +#Set working direcroty +WORKDIR /user_ms + +#Install dependencies +RUN pip install --no-cache-dir -r requirements.txt + + +#Install other libraries in container +RUN apt-get update && apt-get install -y \ + unixodbc \ + unixodbc-dev \ + iputils-ping\ + apt-transport-https \ + gcc\ + curl \ + gnupg \ + freetds-dev \ + tdsodbc \ + && rm -rf /var/lib/apt/lists/* + + +#Add Microsoft repository GPG key +RUN curl https://packages.microsoft.com/keys/microsoft.asc | tee /etc/apt/trusted.gpg.d/microsoft.asc + +#Add the Microsoft SQL Server repository for Debian 12 +RUN curl https://packages.microsoft.com/config/debian/12/prod.list | tee /etc/apt/sources.list.d/mssql-release.list + + +#Add Microsoft GPG key +RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg + +#Add the Microsoft SQL Server repository for Debian 12 +RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/debian/12/prod bookworm main" > /etc/apt/sources.list.d/mssql-release.list + +#Update package list +RUN apt-get update + +#Install ODBC Driver 17 for SQL Server +RUN ACCEPT_EULA=Y apt-get install -y msodbcsql17 + +#Optional: Install mssql-tools and add to PATH +RUN ACCEPT_EULA=Y apt-get install -y mssql-tools +RUN bash -c "echo 'export PATH=\"$PATH:/opt/mssql-tools/bin\"' >> ~/.bashrc && source ~/.bashrc" + + +#RUN echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc && source ~/.bashrc + +#Optional: Install unixodbc-dev and kerberos library +RUN apt-get install -y libgssapi-krb5-2 + +#Set environment variables for ODBC configuration if needed +ENV ODBCINI=/etc/odbc.ini +ENV ODBCSYSINI=/etc + +CMD ["python", "index.py"] \ No newline at end of file diff --git a/User_Management.bak b/User_Management.bak new file mode 100644 index 0000000000000000000000000000000000000000..8286826a43c0de28f2cdc67078940fdafcfd9f9a Binary files /dev/null and b/User_Management.bak differ diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ab488dfb9077cfca361f84c8511c86b3ed935dbb --- /dev/null +++ b/app.yaml @@ -0,0 +1,2 @@ +runtime: python312 +entrypoint: gunicorn -b :$PORT index:app \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e5da06c31592e6410ec992cae27f65004ade98d6 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,10 @@ +from flask import Flask + +from app.models import login + +app = Flask(__name__) + +from app import routes + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/app/__pycache__/config.cpython-311.pyc b/app/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..091813a29718206a89d166b99bce03396b05d34c Binary files /dev/null and b/app/__pycache__/config.cpython-311.pyc differ diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000000000000000000000000000000000000..0405b913851c12c81c205bd6e66277fba2a0278a --- /dev/null +++ b/app/config.py @@ -0,0 +1,11 @@ +import os + + +DEBUG = True +SECRET_KEY = "Group3" + +PORT = 5000 + +KAFKA_SERVER = "kafka:9092" + +HOST = "0.0.0.0" \ No newline at end of file diff --git a/app/controllers/__init__.py b/app/controllers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..83fe4b03fbf1ea1097fdec730685170c3a35cc8d --- /dev/null +++ b/app/controllers/__init__.py @@ -0,0 +1,3 @@ +from flask import Flask +from flask import Blueprint + diff --git a/app/controllers/__pycache__/__init__.cpython-311.pyc b/app/controllers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a4bfd8103bb4380305718ed59e96e027557a8a4 Binary files /dev/null and b/app/controllers/__pycache__/__init__.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/changePasswordController.cpython-311.pyc b/app/controllers/__pycache__/changePasswordController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34cc426ee500d1191ecb52b66c1c4517c902dad9 Binary files /dev/null and b/app/controllers/__pycache__/changePasswordController.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc b/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ce448acfcb8142341af9ce1dbd5583428925b08 Binary files /dev/null and b/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/fetchUsernameController.cpython-311.pyc b/app/controllers/__pycache__/fetchUsernameController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cbe6aa45038b2bd7bda92392953d3e3b023f3a3 Binary files /dev/null and b/app/controllers/__pycache__/fetchUsernameController.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/login.cpython-311.pyc b/app/controllers/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee02eac64b23cc7222f821324eb85aa9d76b8dc0 Binary files /dev/null and b/app/controllers/__pycache__/login.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/loginController.cpython-311.pyc b/app/controllers/__pycache__/loginController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0b17d9c4604c96ba994a28b64fd1eebc7061a71 Binary files /dev/null and b/app/controllers/__pycache__/loginController.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/logoutController.cpython-311.pyc b/app/controllers/__pycache__/logoutController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63d12e36ae470889c341e2c26608db4c1b994b39 Binary files /dev/null and b/app/controllers/__pycache__/logoutController.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/showReviewsController.cpython-311.pyc b/app/controllers/__pycache__/showReviewsController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51d9ad122d7784716c744cb4a8122c6254d70f40 Binary files /dev/null and b/app/controllers/__pycache__/showReviewsController.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/signupController.cpython-311.pyc b/app/controllers/__pycache__/signupController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55cd6b76556ac83d7c5d814dc2a64f828492b907 Binary files /dev/null and b/app/controllers/__pycache__/signupController.cpython-311.pyc differ diff --git a/app/controllers/__pycache__/updateProfileController.cpython-311.pyc b/app/controllers/__pycache__/updateProfileController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b7bc1569e5e102cdf2927d1001d7ef46f330135 Binary files /dev/null and b/app/controllers/__pycache__/updateProfileController.cpython-311.pyc differ diff --git a/app/controllers/changePasswordController.py b/app/controllers/changePasswordController.py new file mode 100644 index 0000000000000000000000000000000000000000..5fbb96dbca89b737017962cde091c6c2b72d7499 --- /dev/null +++ b/app/controllers/changePasswordController.py @@ -0,0 +1,122 @@ +#This page allows users to change their password +# +# +from flask import Blueprint, jsonify, request, session +from models.changePassword import check_old_password, set_new_password +import hashlib +import secrets +import hmac + +change_password_bp = Blueprint("change_password",__name__) + +@change_password_bp.route("/user/change_password", methods=["POST"]) +def change_password(): + + user_id = session.get("user_id") #Get user ID from session + + if user_id:#checks if user is logges in + + if request.method == 'POST': + + #User data from front end + data = request.get_json() + email = data.get("email") + old_password = data.get("old_password") + new_password = data.get("new_password") + + new_encoded_password = generate_password_hash(new_password) + new_password_hash = new_encoded_password["hash"] + new_password_salt = new_encoded_password["salt"] + new_password_iterations = new_encoded_password["iterations"] + + #collect old password, user id and email into json + old_auth = { + "user_id" : user_id, + "email" : email, + "password": old_password + } + + #user_old_auth = check_old_password(old_auth) + + + #send old password to database + old_auth_info, value = check_old_password(old_auth) #function returns certain columns collected from database + + if value == 1: #if user exists in database + + old_password_hash = old_auth_info.get("PasswordHash") + old_password_salt = old_auth_info.get("PasswordSalt") + old_password_iterations = old_auth_info.get("Iterations") + + + #password authentication. Calls password fucntions + old_password_info = generate_password_hash(old_password) + is_correct = verify_password(old_password_info, old_password, old_password_salt, old_password_iterations, old_password_hash) + + if is_correct == True:#if password is correct + + #passes new password information to json + new_auth = { + "user_id" : user_id, + "email" : email, + "password": new_password, + "hash": new_password_hash, + "salt": new_password_salt, + "iterations": new_password_iterations + } + + #sends new password to database + new_auth_info = set_new_password(new_auth) + + + + response_data = {"message":"Password is correct"} + return jsonify(new_auth_info, user_id) + + else: + response_data = {"error":"Old password is incorrect", "email": email} + return jsonify(response_data) + + else: + return {"error" : "Email does not exist"} + + + return {"error" : "null"} + + else: + return {"error" : "User not logged in"} + + + + + +#password encoding +def generate_password_hash(password): + # Generate a 16-byte salt + salt = secrets.token_bytes(16) + # Define the number of iterations + iterations = 100000 + # Generate the hash using PBKDF2-HMAC-SHA-256 + hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, iterations) + + # Return the salt, iterations, and hash, encoded in a way that can be stored in the database + return { + 'salt': salt.hex(), + 'iterations': iterations, + 'hash': hash.hex() + } + + +#password verification +def verify_password(stored_password_info, submitted_password, salt, iterations, user_hash): + # Convert the stored salt back to bytes + salt = bytes.fromhex(salt) + # Use the same number of iterations as when the password was hashed + iterations = iterations + # Hash the submitted password with the stored salt and iterations + hash = hashlib.pbkdf2_hmac('sha256', submitted_password.encode(), salt, iterations) + + # Compare the newly generated hash with the stored hash + # Convert the generated hash to hex for comparison + + return hmac.compare_digest(hash.hex(), user_hash) \ No newline at end of file diff --git a/app/controllers/deleteProfileController.py b/app/controllers/deleteProfileController.py new file mode 100644 index 0000000000000000000000000000000000000000..f29f3689923437d63695693ebaf81f1be31a2dcb --- /dev/null +++ b/app/controllers/deleteProfileController.py @@ -0,0 +1,37 @@ +#This section allows a user to delete their profile +# + +from flask import Blueprint, jsonify, request, session +from models.deleteProfile import delete + +deleteProfile_bp = Blueprint("deleteProfile",__name__) + +@deleteProfile_bp.route("/user/deleteProfile", methods=["POST"]) +def deleteProfile(): + + #collects user id from session + user_id = session.get("user_id") + + if user_id:#checks if user is logged in + + if request.method == 'POST': + + #User data from front end + data = request.get_json() + email = data.get("email") + + user_info = { + "email" : email, + "user_id" : user_id + } + + user = delete(user_info) #sends user data to database + + + return jsonify(user, user_id) + + else: + return {"message" : "null"} + + else: + return {"error" : "User not logged in"} \ No newline at end of file diff --git a/app/controllers/fetchUsernameController.py b/app/controllers/fetchUsernameController.py new file mode 100644 index 0000000000000000000000000000000000000000..00380458a388542e5aaa69a20561324d7f152a0c --- /dev/null +++ b/app/controllers/fetchUsernameController.py @@ -0,0 +1,30 @@ +from flask import Blueprint, jsonify, request, json, session +from models.fetchUsername import get_username + +fetch_username_bp = Blueprint("getUsername",__name__) + +#This function is for the product microservice. It makes a call to this +#endpoint to fetch a user's username when they want to add a product review +@fetch_username_bp.route("/user/getUsername", methods=["POST"]) +def username(): + + user_id = session.get("user_id") #gets session data + + #if user_id: #if user is logged in + + if request.method == 'POST': + + #User data from front end + data = request.get_json() + userID = data.get("id") + + + + username= get_username(userID) #Send user info to database + + return jsonify({"username" : username}), 200 + + else: + return {"error" : "null"} + #else: + # return {"error" : "User not logged in"} \ No newline at end of file diff --git a/app/controllers/getUsersController.py b/app/controllers/getUsersController.py new file mode 100644 index 0000000000000000000000000000000000000000..e2a9bd4c09b48d0beb756dddd03e66562435a8ba --- /dev/null +++ b/app/controllers/getUsersController.py @@ -0,0 +1,19 @@ +from flask import Blueprint, jsonify, request, json, session, redirect +from config import KAFKA_SERVER + +from models.getUsers import fetch_user_info + +from kafka import KafkaConsumer + +consumer = KafkaConsumer("review_events", bootstrap_servers=KAFKA_SERVER) + +def getusers(user_id): + + pass + + +for message in consumer: + + event_data = json.loads(message.value.decode()) + user_id = event_data["user_id"] + username = getusers(user_id) \ No newline at end of file diff --git a/app/controllers/loginController.py b/app/controllers/loginController.py new file mode 100644 index 0000000000000000000000000000000000000000..232350cda6d673061fd1a75a26a98ddcd07ab8de --- /dev/null +++ b/app/controllers/loginController.py @@ -0,0 +1,93 @@ +#This section allows a user to log in + +from flask import Blueprint, jsonify, request, session +from models.login import fetch_user +from models.login import fetch_password +import hashlib +import secrets +import hmac + +login_bp = Blueprint("login",__name__) + +@login_bp.route("/login", methods=["POST"]) +def login(): + + if request.method == 'POST': + + #User data from front end + data = request.get_json() + email = data.get("email") + password = data.get("password") + + + + user = fetch_user(email) #Collect user data from database + + #User authentication + if user is not None: #If database found matching email the user entered + + user_email = user.get("Email") #User email from database + + + if user_email == email: #Checks if email returned from database is the same as what user entered + + auth = fetch_password(user_email) #function returns certain columns collected from database + + user_hash = auth.get("PasswordHash") + user_salt = auth.get("PasswordSalt") + user_iterations = auth.get("Iterations") + + + #password authentication + password_info = generate_password_hash(password) + is_correct = verify_password(password_info, password, user_salt, user_iterations, user_hash) + + if is_correct == True: #if password is correct + session["user_id"] = user.get("UserID") + response_data = {"message":"Login Sucessful", "email": email, "session" : session["user_id"]} + return jsonify(response_data) + + else: + response_data = {"message":"Email or password incorrect", "email": email} + return jsonify(response_data) + + + else: + return ("Email does not exist") + + else: + response_data = {"message":"Email does not exist", "email": email} + return jsonify(response_data) + + return {"message" : "null"} + + +#password encryption +def generate_password_hash(password): + # Generate a 16-byte salt + salt = secrets.token_bytes(16) + # Define the number of iterations + iterations = 100000 + # Generate the hash using PBKDF2-HMAC-SHA-256 + hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, iterations) + + # Return the salt, iterations, and hash, encoded in a way that can be stored in the database + return { + 'salt': salt.hex(), + 'iterations': iterations, + 'hash': hash.hex() + } + +#password verification +def verify_password(stored_password_info, submitted_password, salt, iterations, user_hash): + # Convert the stored salt back to bytes + salt = bytes.fromhex(salt) + # Use the same number of iterations as when the password was hashed + iterations = iterations + # Hash the submitted password with the stored salt and iterations + hash = hashlib.pbkdf2_hmac('sha256', submitted_password.encode(), salt, iterations) + + # Compare the newly generated hash with the stored hash + # Convert the generated hash to hex for comparison + + return hmac.compare_digest(hash.hex(), user_hash) \ No newline at end of file diff --git a/app/controllers/logoutController.py b/app/controllers/logoutController.py new file mode 100644 index 0000000000000000000000000000000000000000..e296c4dae0172fed0af189b5005cfbef8d1a021c --- /dev/null +++ b/app/controllers/logoutController.py @@ -0,0 +1,23 @@ +#This section allows a user to log out + +from flask import Blueprint, jsonify, request, json, session, redirect + + +logout_bp = Blueprint("logout",__name__) + +@logout_bp.route("/logout", methods=["POST"]) +def logout(): + + user_id = session.get("user_id") #get session data + + if user_id: #if user is logged in + + if request.method == 'POST': + + session.pop("user_id", None) #deletes session + return ({"message" : "Log out successful"}) + + else: + return {"error" : "null"} + else: + return {"error" : "User not logged in"} \ No newline at end of file diff --git a/app/controllers/showReviewsController.py b/app/controllers/showReviewsController.py new file mode 100644 index 0000000000000000000000000000000000000000..42d853475f2622e476bcb9ca5ed87e0c8d58e472 --- /dev/null +++ b/app/controllers/showReviewsController.py @@ -0,0 +1,56 @@ +from flask import Blueprint, jsonify, request, session +from kafka import KafkaConsumer +import json +from config import KAFKA_SERVER + +show_reviews_bp = Blueprint("showReviews", __name__) + + +consumer_conf = { + "bootstrap_servers": KAFKA_SERVER, + "group_id": "show_reviews_group", + "auto_offset_reset": "earliest" +} + +#Function to consume reviews published by product microservice +def consume_reviews(num_reviews=1): + consumer = KafkaConsumer("customer_reviews", **consumer_conf) + reviews = [] + + + for message in consumer: + + review_data_str = message.value.decode("utf-8") + + + review_data = json.loads(review_data_str) + + + reviews.append(review_data) + + + if len(reviews) >= num_reviews: + break + + + consumer.close() + + return reviews + + +#Route to show reviews for a user +@show_reviews_bp.route("/user/showReviews", methods=["POST"]) +def show_reviews(): + #Collect user id from session + user_id = session.get("user_id") + + if user_id: # Check if user is logged in + if request.method == 'POST': + #Call function to consume reviews + reviews = consume_reviews() + + return jsonify(reviews) + else: + return {"message": "null"} + else: + return {"error": "User not logged in"} diff --git a/app/controllers/signupController.py b/app/controllers/signupController.py new file mode 100644 index 0000000000000000000000000000000000000000..0ec066371a3283032f8f059a08a2e7eab3ea1d78 --- /dev/null +++ b/app/controllers/signupController.py @@ -0,0 +1,71 @@ +#This section allows a user to create a profile + +from flask import Blueprint, jsonify, request, json +from models.signup import new_user +import hashlib +import secrets + +signup_bp = Blueprint("signup",__name__) + +@signup_bp.route("/signup", methods=["POST"]) +def signup(): + + if request.method == 'POST': + + #User data from front end + data = request.get_json() + email = data.get("email") + first_name = data.get("first_name") + last_name = data.get("last_name") + location = data.get("location") + gender = data.get("gender") + password = data.get("password") + encoded_password = generate_password_hash(password) + hash = encoded_password["hash"] + salt = encoded_password["salt"] + iterations = encoded_password["iterations"] + + if email.strip() != "": #checks if email is not empty (email cannot be null) + + # Create a dictionary from user data + user_data = { + "email": email, + "first_name": first_name, + "last_name": last_name, + "location": location, + "gender": gender, + "password": password, + "hash": hash, + "salt": salt, + "iterations": iterations + } + + # Convert to JSON + json_user_data = json.dumps(user_data) + + + update = new_user(user_data) #Send user info to database + + return jsonify(update) + + else: + return {"error" : "email cannot be empty"} + + return {"error" : "null"} + + +#This function encrypts the user's password +def generate_password_hash(password): + # Generate a 16-byte salt + salt = secrets.token_bytes(16) + # Define the number of iterations + iterations = 100000 + # Generate the hash using PBKDF2-HMAC-SHA-256 + hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, iterations) + + # Return the salt, iterations, and hash, encoded in a way that can be stored in the database + return { + 'salt': salt.hex(), + 'iterations': iterations, + 'hash': hash.hex() + } diff --git a/app/controllers/updateProfileController.py b/app/controllers/updateProfileController.py new file mode 100644 index 0000000000000000000000000000000000000000..bca3119885f245ad049dc58820fc7c92a1d5aceb --- /dev/null +++ b/app/controllers/updateProfileController.py @@ -0,0 +1,56 @@ +#This section allows a user to update their profile + +from flask import Blueprint, jsonify, request, json, session +from models.updateProfile import update_user +from models.updateProfile import fetch_user_info + +from events.eventDefinitions import updated_username +from publishers.kafkaPublishers import publish_username_updated_event + +update_profile_bp = Blueprint("update",__name__) + +@update_profile_bp.route("/user/update", methods=["POST"]) +def update_profile(): + + user_id = session.get("user_id") #gets session data + + if user_id: #if user is logged in + + user_info = fetch_user_info(user_id) + print(jsonify(user_info)) + + if request.method == 'POST': + + #User data from front end + data = request.get_json() + email = data.get("email") + first_name = data.get("first_name") + last_name = data.get("last_name") + location = data.get("location") + gender = data.get("gender") + + + # Create a json object from user data + user_data = { + "user_id" : user_id, + "email": email, + "first_name": first_name, + "last_name": last_name, + "location": location, + "gender": gender, + } + + update = update_user(user_data) #Send user info to database + + if "message" in update: + + event_data = {"user_id" : user_id, "new_username" : user_data["first_name"]} + event_message = updated_username(user_id, user_data["first_name"]) + publish_username_updated_event(event_data) + + return jsonify({"Update Status": update, "Username" : user_data["first_name"], "User ID" : user_id, "Event message" : event_message}) + + else: + return {"error" : "null"} + else: + return {"error" : "User not logged in"} \ No newline at end of file diff --git a/app/events/__init__.py b/app/events/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app/events/__pycache__/__init__.cpython-311.pyc b/app/events/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95be18671d60d24a8dd5c89660bea97b034080d2 Binary files /dev/null and b/app/events/__pycache__/__init__.cpython-311.pyc differ diff --git a/app/events/__pycache__/eventDefinitions.cpython-311.pyc b/app/events/__pycache__/eventDefinitions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26189d001a07adbc3c51f25578fd46235d1830b5 Binary files /dev/null and b/app/events/__pycache__/eventDefinitions.cpython-311.pyc differ diff --git a/app/events/eventDefinitions.py b/app/events/eventDefinitions.py new file mode 100644 index 0000000000000000000000000000000000000000..bb88066408e7c588d1eeadaec115cac9b4c2f06a --- /dev/null +++ b/app/events/eventDefinitions.py @@ -0,0 +1,5 @@ +def updated_username(user_id, new_username): + + return {"event_type": "profile_updated", + "user_id": user_id, + "new_username": new_username} \ No newline at end of file diff --git a/app/index.py b/app/index.py new file mode 100644 index 0000000000000000000000000000000000000000..2ee61fb8d8d7cd0d715eb4c479b74b65da147209 --- /dev/null +++ b/app/index.py @@ -0,0 +1,80 @@ +from flask import Flask, jsonify, redirect, json, url_for, request, render_template, make_response, session, abort +from flask_cors import CORS +from flask import jsonify +from controllers.loginController import login_bp +from controllers.signupController import signup_bp +from controllers.updateProfileController import update_profile_bp +from controllers.changePasswordController import change_password_bp +from controllers.deleteProfileController import deleteProfile_bp +from controllers.logoutController import logout_bp +from controllers.fetchUsernameController import fetch_username_bp +from controllers.showReviewsController import show_reviews_bp + +from config import DEBUG, SECRET_KEY, PORT, HOST +import os +import requests +import publishers + + + +app = Flask(__name__) +CORS(app) + + +@app.route('/') +def index(): + return render_template("index.html") + + +@app.route("/hello/<int:score>") +def hello_user(score): + return render_template("hello.html", marks=score) + + +app.register_blueprint(login_bp) + +app.register_blueprint(signup_bp) + +app.register_blueprint(update_profile_bp) + +app.register_blueprint(change_password_bp) + +app.register_blueprint(deleteProfile_bp) + +app.register_blueprint(logout_bp) + +app.register_blueprint(fetch_username_bp) + +app.register_blueprint(show_reviews_bp) + +publishers.create_profile_updated_topic() + + +#Check if application is running in docker to collect or set secret key +try: + + #Check for the existence of the /proc/self/cgroup file + with open("/proc/self/cgroup", "r") as cgroup_file: + cgroup_info = cgroup_file.read() + + #Check if the cgroup information contains 'docker' keyword + if 'docker' in cgroup_info: + print("Running inside Docker container") + app.secret_key = os.environ.get('SECRET_KEY') + +except FileNotFoundError: + # If the file doesn't exist + print("Running on a local Windows machine") + + app.secret_key = SECRET_KEY + + +@app.route("/userIDs", methods=["POST"]) +def userIDs(): + + #ids = request.json + #print(ids) + return 'hi' + +if __name__ == '__main__': + app.run(host=HOST, debug=DEBUG, port=PORT) \ No newline at end of file diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d55ce4a0c8e0748e87cd3ecae1b6fe12294bdae3 --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1,15 @@ +from flask import Flask +from flask_cors import CORS + + +#from app.models import models + +app = Flask(__name__) +CORS(app) + +#db = sqlAlchemy + +#from app import routes + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/app/models/__pycache__/__init__.cpython-311.pyc b/app/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfb0366dfbe4ee46a4cd5603bac50bd5dcb0efcf Binary files /dev/null and b/app/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/app/models/__pycache__/changePassword.cpython-311.pyc b/app/models/__pycache__/changePassword.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efbc1602a81cf43627230b17ff6be9b650f9cba8 Binary files /dev/null and b/app/models/__pycache__/changePassword.cpython-311.pyc differ diff --git a/app/models/__pycache__/database_connection.cpython-311.pyc b/app/models/__pycache__/database_connection.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d54224fd06a5210d5b85dad1790380d510110bd7 Binary files /dev/null and b/app/models/__pycache__/database_connection.cpython-311.pyc differ diff --git a/app/models/__pycache__/deleteProfile.cpython-311.pyc b/app/models/__pycache__/deleteProfile.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11cd5e31a86ac49992cc243db920bc089e02298a Binary files /dev/null and b/app/models/__pycache__/deleteProfile.cpython-311.pyc differ diff --git a/app/models/__pycache__/fetchUsername.cpython-311.pyc b/app/models/__pycache__/fetchUsername.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae95206848ec046c1557589c2b224c793de637e2 Binary files /dev/null and b/app/models/__pycache__/fetchUsername.cpython-311.pyc differ diff --git a/app/models/__pycache__/getUsers.cpython-311.pyc b/app/models/__pycache__/getUsers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd54bdac2b3189ffb26e70c80dcb6d7a9d4b3f61 Binary files /dev/null and b/app/models/__pycache__/getUsers.cpython-311.pyc differ diff --git a/app/models/__pycache__/login.cpython-311.pyc b/app/models/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b77602b227dc0fa7d1be7ce1a7e1b0b2398dca15 Binary files /dev/null and b/app/models/__pycache__/login.cpython-311.pyc differ diff --git a/app/models/__pycache__/models.cpython-311.pyc b/app/models/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c5497a917a5f33bfe0edde28b561ef47b6a7caf Binary files /dev/null and b/app/models/__pycache__/models.cpython-311.pyc differ diff --git a/app/models/__pycache__/signup.cpython-311.pyc b/app/models/__pycache__/signup.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94a888ae05aa3787387431d65168edf553a0a2cf Binary files /dev/null and b/app/models/__pycache__/signup.cpython-311.pyc differ diff --git a/app/models/__pycache__/updateProfile.cpython-311.pyc b/app/models/__pycache__/updateProfile.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3cc094c4a2bb3f0c99ed37c439242aab85aaf1a Binary files /dev/null and b/app/models/__pycache__/updateProfile.cpython-311.pyc differ diff --git a/app/models/changePassword.py b/app/models/changePassword.py new file mode 100644 index 0000000000000000000000000000000000000000..3e7403d667fa12de9e122853552fb14da27210a1 --- /dev/null +++ b/app/models/changePassword.py @@ -0,0 +1,107 @@ +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + + +def check_old_password(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + email = data["email"] + user_id = data["user_id"] + + + + #Check if the email already exists + email_check_query = "SELECT COUNT(*) FROM dbo.User_table WHERE UserID = ?" + cursor.execute(email_check_query, user_id) #executes the query + count = cursor.fetchone()[0] + + if count == 0: + return ({"Error" : "Email does not exist"}, 0) + + else: + #selects password information from database + query = "SELECT t1.Email, t1.UserID, t2.Iterations, t2.PasswordHash, t2.PasswordSalt FROM dbo.User_table as t1 INNER JOIN dbo.AuthInfo as t2 ON t1.UserID = t2.UserID where t1.UserID= ?" + + cursor.execute(query, user_id) #executes query + + row = cursor.fetchone() + + columns = [column[0] for column in cursor.description] + user_data = dict(zip(columns, row)) + + #connection.close() + + return (user_data, 1) + + except pyodbc.Error as e: #more error handling + print(f"Database error in check_old_password: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in check_old_password: {e}") + connection.rollback() + return {"Error" : "Unexpected error"} + + finally: + if cursor: + cursor.close() + if connection: + connection.close() + + +def set_new_password(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + email = data["email"] + user_id = data["user_id"] + + + #selects user ID + select_userID_query = "SELECT UserID from dbo.User_table where UserID = ?" + cursor.execute(select_userID_query, user_id) + UserID = cursor.fetchone()[0] + + + #updates password + update_authinfo_query = '''UPDATE dbo.AuthInfo + SET PasswordHash = ?, PasswordSalt = ?, Iterations = ?, TempPlainText = ? + WHERE UserID = ?''' + + + cursor.execute(update_authinfo_query, (data["hash"], data["salt"], data["iterations"], data["password"], UserID)) + + connection.commit() + + + #connection.close() + + return {"Message" : "Password updated successfully"} + + + + except pyodbc.Error as e: #more error handling + print(f"Database error in set_new_password: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in set_new_password: {e}") + connection.rollback() + return {"Error" : "Unexpected error"} + + finally: + if cursor: + cursor.close() + if connection: + connection.close() \ No newline at end of file diff --git a/app/models/database_connection.py b/app/models/database_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..945742875f3b666e8427edc3e662536a8e6ace70 --- /dev/null +++ b/app/models/database_connection.py @@ -0,0 +1,24 @@ +import pyodbc +import os + +print("Outside db connection function") +#Connect to database +def connect_db(): + print("In CONNECT DB") + try: + + server = "35.197.217.212" + database = "userdatabase" + username = "sqlserver" + password = "WebTechGroup3" + driver = "ODBC Driver 17 for SQL Server" + + connection_string = f"DRIVER={driver};SERVER={server};DATABASE={database};UID={username};PWD={password};Trusted_Connection=no;" + + return pyodbc.connect(connection_string) + + except Exception as e: + # If the file doesn't exist + print("Unexpected error occured {e}") + + return False diff --git a/app/models/deleteProfile.py b/app/models/deleteProfile.py new file mode 100644 index 0000000000000000000000000000000000000000..7972b969a1ba2a46cee56c5a76c0310f2be1b219 --- /dev/null +++ b/app/models/deleteProfile.py @@ -0,0 +1,59 @@ +#from app import db +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + + +def delete(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + email = data["email"] + user_id = data["user_id"] + + # select user id from database + id_check_query = "SELECT UserID from dbo.User_table WHERE UserID= ?" + cursor.execute(id_check_query, user_id) + user_row = cursor.fetchone() + + if user_row: + userID = user_row[0] + else: + return {"Error": "User not found"} + + #delete from authinfo table first because of foreign key constraint + delete_authinfo_query = '''DELETE FROM dbo.AuthInfo WHERE UserID= ?''' + cursor.execute(delete_authinfo_query, userID) + + + #delete user info from user table + delete_user_query = '''DELETE FROM dbo.User_table WHERE UserID = ?''' + cursor.execute(delete_user_query, userID) + + + + + #commit changes to database if no errors + connection.commit() + + return {"message" : "Account successfully deleted"} + + except pyodbc.Error as e: #more error handling + print(f"Database error in delete: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in delete: {e}") + connection.rollback() + return {"Error" : "Unexpected error"} + + finally: + if cursor: + cursor.close() + if connection: + connection.close() \ No newline at end of file diff --git a/app/models/fetchUsername.py b/app/models/fetchUsername.py new file mode 100644 index 0000000000000000000000000000000000000000..98c4cb135760391159dec56757fa7d2ddf267c88 --- /dev/null +++ b/app/models/fetchUsername.py @@ -0,0 +1,39 @@ +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + +def get_username(id): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + query = "SELECT Username FROM dbo.User_table where UserID = ?" + cursor.execute(query, id) + + row = cursor.fetchone() #fetch data + + if row: + return row[0] + else: + return None + + #connection.close() + + return username + + except pyodbc.Error as e: #error handling + print(f"Database error in fetch_user_info: {e}") + return None + + except Exception as e: #error handling + print(f"Unexpected error occured in get_username: {e}") + return None + + finally: + if cursor: + cursor.close() + if connection: + connection.close() diff --git a/app/models/getUsers.py b/app/models/getUsers.py new file mode 100644 index 0000000000000000000000000000000000000000..f056e2dea7fe5579b85688e91003cd420b0dc591 --- /dev/null +++ b/app/models/getUsers.py @@ -0,0 +1,38 @@ +#from app import db +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + +def fetch_user_info(id): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + query = "SELECT First_name, Last_name FROM dbo.User_table where UserID = ?" + cursor.execute(query, id) + + row = cursor.fetchone() #fetch data + + columns = [column[0] for column in cursor.description] + user_data = dict(zip(columns, row)) + + #connection.close() + + return user_data + + except pyodbc.Error as e: #error handling + print(f"Database error in fetch_user_info: {e}") + return None + + except Exception as e: #error handling + print(f"Unexpected error occured in fetch_user_info: {e}") + return None + + finally: + if cursor: + cursor.close() + if connection: + connection.close() \ No newline at end of file diff --git a/app/models/login.py b/app/models/login.py new file mode 100644 index 0000000000000000000000000000000000000000..f790b89365c7182f6c5345ca70a2a2436fa36c95 --- /dev/null +++ b/app/models/login.py @@ -0,0 +1,72 @@ +#from app import db +import pyodbc +from flask import jsonify +from models.database_connection import connect_db + +#Function to get user info +def fetch_user(email): + + try: #error handling + print("In FETCH USER") + connection = connect_db() + cursor = connection.cursor() + + query = "SELECT * FROM dbo.User_table where Email = ?" + cursor.execute(query, email) + + row = cursor.fetchone() #fetch data + + columns = [column[0] for column in cursor.description] + user_data = dict(zip(columns, row)) + + #connection.close() + + return user_data + + except pyodbc.Error as e: #error handling + print(f"Database error in fetch_user: {e}") + return None + + except Exception as e: #error handling + print(f"Unexpected error occured in fetch_user: {e}") + return None + + + + + + +#Fetch user's password +def fetch_password(email): + + try: + + connection = connect_db() + cursor = connection.cursor() + + query = "SELECT t1.Email, t1.UserID, t2.Iterations, t2.PasswordHash, t2.PasswordSalt FROM dbo.User_table as t1 INNER JOIN dbo.AuthInfo as t2 ON t1.UserID = t2.UserID where Email= ?" + + cursor.execute(query, email) + + row = cursor.fetchone() + + if row is None: + return {"message" : "Email does not exist"} + + else: + columns = [column[0] for column in cursor.description] + user_data = dict(zip(columns, row)) + + #connection.close() + + return user_data + + except pyodbc.Error as e: + print(f"Database error in fetch_user: {e}") + return None + + except Exception as e: + print(f"Unexpected error occured in fetch_user: {e}") + return None + + \ No newline at end of file diff --git a/app/models/signup.py b/app/models/signup.py new file mode 100644 index 0000000000000000000000000000000000000000..63031efd240aead253b9dfcab8b2ac556cd55b36 --- /dev/null +++ b/app/models/signup.py @@ -0,0 +1,63 @@ +#from app import db +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + +#Create a new user +def new_user(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + email = data["email"] + + # Check if the email already exists + email_check_query = "SELECT COUNT(*) FROM dbo.User_table WHERE Email = ?" + cursor.execute(email_check_query, email) + count = cursor.fetchone()[0] + + if count > 0: + return {"Error": "Email already exists"} + + + #insert data into user table + insert_user_query = '''INSERT INTO dbo.User_table (Username, Email, First_Name, Last_Name, Tenure_Months, Location, Gender) +VALUES (?, ?, ?, ?, ?, ?, ?);''' + cursor.execute(insert_user_query, (data["first_name"], data["email"], data["first_name"], data["last_name"], " ", data["location"], data["gender"])) + + + UserID_query = "SELECT UserID FROM dbo.User_table WHERE Email= ?" + cursor.execute(UserID_query, email) + UserID = cursor.fetchone()[0] + + + #Insert password into authentication table + insert_authinfo_query = '''INSERT INTO dbo.AuthInfo (PasswordHash, PasswordSalt, Iterations, TempPlainText, UserID) +VALUES (?, ?, ?, ?, ?);''' + cursor.execute(insert_authinfo_query, (data["hash"], data["salt"], data["iterations"], data["password"], UserID)) + + + + #commit changes to database if no errors + connection.commit() + + return {"message" : "New user info added successfully"} + + except pyodbc.Error as e: #more error handling + print(f"Database error in new_user: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in new_user: {e}") + connection.rollback() + return {"Error" : "Unexpected error"} + + finally: + if cursor: + cursor.close() + if connection: + connection.close() \ No newline at end of file diff --git a/app/models/updateProfile.py b/app/models/updateProfile.py new file mode 100644 index 0000000000000000000000000000000000000000..0c70bedb121f72626f28135dc13b5d22fd962f08 --- /dev/null +++ b/app/models/updateProfile.py @@ -0,0 +1,86 @@ +#from app import db +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + +def fetch_user_info(id): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + query = "SELECT * FROM dbo.User_table where UserID = ?" + cursor.execute(query, id) + + row = cursor.fetchone() #fetch data + + columns = [column[0] for column in cursor.description] + user_data = dict(zip(columns, row)) + + #connection.close() + + return user_data + + except pyodbc.Error as e: #error handling + print(f"Database error in fetch_user_info: {e}") + return None + + except Exception as e: #error handling + print(f"Unexpected error occured in fetch_user_info: {e}") + return None + + finally: + if cursor: + cursor.close() + if connection: + connection.close() + + + +def update_user(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + user_id = data["user_id"] + + #insert data into user table + update_user_query = '''UPDATE dbo.User_table + SET + Username= ?, + First_Name= ?, + Last_Name= ?, + Location= ?, + Gender= ? + WHERE + UserID= ?''' + + cursor.execute(update_user_query, (data["first_name"], data["first_name"], data["last_name"], data["location"], data["gender"], user_id)) + + + #commit changes to database if no errors + connection.commit() + + return {"message" : "Profile updated successfully"} + + + + except pyodbc.Error as e: #more error handling + print(f"Database error in update_user: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in update_user: {e}") + connection.rollback() + return {"Error" : "Unexpected error"} + + finally: + if cursor: + cursor.close() + if connection: + connection.close() \ No newline at end of file diff --git a/app/publishers/__init__.py b/app/publishers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..beb65e5ef1e4ca89b1eb1f7148c7aa845f8a7348 --- /dev/null +++ b/app/publishers/__init__.py @@ -0,0 +1 @@ +from publishers.kafkaPublishers import create_profile_updated_topic \ No newline at end of file diff --git a/app/publishers/__pycache__/__init__.cpython-311.pyc b/app/publishers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e5ad7960c3085c3269be00737e82ddc12a6c966 Binary files /dev/null and b/app/publishers/__pycache__/__init__.cpython-311.pyc differ diff --git a/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc b/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab88574606876c33991d2d1f9cf5c27a3e73f26b Binary files /dev/null and b/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc differ diff --git a/app/publishers/kafkaPublishers.py b/app/publishers/kafkaPublishers.py new file mode 100644 index 0000000000000000000000000000000000000000..279b149bd37ce8f3c30a1c38c406d8eb1bf06048 --- /dev/null +++ b/app/publishers/kafkaPublishers.py @@ -0,0 +1,47 @@ +from kafka import KafkaProducer +from kafka.admin import KafkaAdminClient, NewTopic + +from config import KAFKA_SERVER + +import json + +producer = KafkaProducer(bootstrap_servers=KAFKA_SERVER) + + +#Creates the topic +def create_profile_updated_topic(): + + admin_client = KafkaAdminClient(bootstrap_servers=KAFKA_SERVER) + + # Define the topic name + topic_name = "profile_updated_topic" + num_partitions = 1 + replication_factor = 1 + + #Get topics + topic_metadata = admin_client.list_topics() + + #Check if the topic exists + if topic_name not in topic_metadata: + + new_topic = NewTopic(name=topic_name, num_partitions=num_partitions, replication_factor=replication_factor) + + admin_client.create_topics(new_topics=[new_topic], validate_only=False) + + +#Function is called in updateProfileControllers.py +#Topic message is collected from ProductMicroservice/Subsribers/updateUsernameSubscriber.py +def publish_username_updated_event(event_data): + + event_json = json.dumps(event_data) + #Publish the event to the Kafka topic + data_to_send = producer.send("profile_updated_topic", value=event_json.encode("utf-8")) + try: + record_metadata = data_to_send.get(timeout=10) + print("Message sent successfully!") + print("Topic:", record_metadata.topic) + print("Partition:", record_metadata.partition) + print("Offset:", record_metadata.offset) + except Exception as e: + print("Failed to send message:", e) + producer.flush() \ No newline at end of file diff --git a/app/run.py b/app/run.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..6207c5207b735653b32e6ad59ac7d969cdfab4eb --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,6 @@ +<html> + <head></head> + <body> + <p>HELLO</p> + </body> +</html> \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..8ff3f2bcf3113452668a674fa0aea07c9c7f5cb7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.2" + +services: + + user-microservice: + build: + context: . + dockerfile: Dockerfile + no_cache: true + image: user-microservice:3.0 + container_name: user-microservice + ports: + - "5000:5000" + environment: + #DATABASE_URL: "DRIVER={ODBC Driver 17 for SQL Server};SERVER=user-database,1433;DATABASE=User_Management;UID=sa;PWD=WebTechGroup3;" + KAFKA_SERVER: "kafka:9092" + SECRET_KEY: Group3 + networks: + - kafka_network + +networks: + kafka_network: + external: true + #driver: bridge \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..268cd4a00310bd1e5a014d92a84e48a52a1cb1ad --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Start SQL Server in the background +/opt/mssql/bin/sqlservr & + +# Wait for 5 minutes (300 seconds) +sleep 300 + +# Log in and restore database +/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -Q "RESTORE DATABASE User_Management FROM DISK = '/var/opt/mssql/data/User_Management.bak' WITH MOVE 'User_Management.mdf' TO '/var/opt/mssql/data/User_Management.mdf', MOVE 'User_Management_log.ldf' TO '/var/opt/mssql/data/User_Management_log.ldf';" + +# Keep the script running indefinitely +wait %1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..aed8ace483f880061abeee73b97954403c1890b1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +Flask==3.0.2 +Flask-Cors==4.0.0 +gunicorn==21.2.0 +kafka-python==2.0.2 +pyodbc==5.1.0 +blinker==1.7.0 +click==8.1.7 +itsdangerous==2.1.2 +Jinja2==3.1.2 +Werkzeug==3.0.1 +boto3==1.34.71 +colorama==0.4.6 +MarkupSafe==2.1.3 +requests==2.31.0 \ No newline at end of file