diff --git a/Kafka/docker-compose.yml b/Kafka/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ac67edd7f1578e2ecd771e04f017a55d135c183a --- /dev/null +++ b/Kafka/docker-compose.yml @@ -0,0 +1,44 @@ +version: "3" + +services: + zookeeper: + image: wurstmeister/zookeeper + container_name: zookeeper + ports: + - "2182:2182" + networks: + - kafka_network + + kafka: + image: wurstmeister/kafka + container_name: kafka + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_HOST_NAME: kafka + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_BOOTSTRAP_SEVERS: kafka:9092 + networks: + - kafka_network + + user-microservice: + image: user-microservice:1.0 + container_name: user-microservice + depends_on: + - kafka + - zookeeper + networks: + - kafka_network + + product-microservice: + image: product-microservice:1.0 + container_name: product-microservice + depends_on: + - kafka + - zookeeper + networks: + - kafka_network + +networks: + kafka_network: + driver: bridge \ No newline at end of file diff --git a/Product_MicroService_Group3/Dockerfile b/Product_MicroService_Group3/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e51666e85b66d04d93dd8ed47ffa291991a71152 --- /dev/null +++ b/Product_MicroService_Group3/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.11.4 + +COPY requirements.txt /product_ms/ + +COPY app /product_ms/ + +WORKDIR /product_ms + +RUN pip install --no-cache-dir -r requirements.txt + + +RUN apt-get update && apt-get install -y \ + unixodbc \ + unixodbc-dev \ + freetds-dev \ + tdsodbc \ + && rm -rf /var/lib/apt/lists/* + +# 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/Product_MicroService_Group3/README.md b/Product_MicroService_Group3/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4f7a8a01c5465f07c193d02dec40ceaec339e742 --- /dev/null +++ b/Product_MicroService_Group3/README.md @@ -0,0 +1,31 @@ +#ReadME +<h1>Folders and Files in the Product Microservice </h1> + + + +<h4>1. Controllers/: <h4> + +<p>a. productHomeController.py: This page is the landing page of the application. It gets product data from the database by category using a POST method. </p><br> + +<p>b. getProductController.py: A user is redirected to this page after they have clicked on a product. It shows information of that product – image, name, price, reviews, ratings. It uses a GET request method to get the product ID. The product ID is sent to the database. </p><br> + +<p>c. addReviewController.py: This section allows a user to add a review to a product. It first of all uses a GET request to get the product ID. It first of all checks if a user's session is active and if they are logged in. If the user is not logged in, it throws an error and asks them to log in. +An HTTP request is also made to the User Microservice to get the username of the user leaving review. This is because the username is stored in the ReviewsandRatings table to avoid always having to make requests to the User Microservice to retrieve names of users when displaying all the reviews. On the same page, a POST request is also used when a user clicks the button to add the review. After the user has posted the review, the username gotten from the microservice, product ID, review and rating are sent to the database. </p><br> + + + +<h4>2. Models/: </h4> + +<p>a. productHome.py: This page sends product information by category from the database to "productHomeController.py". </p><br> + +<p>b. getProduct.py: This page has a function that collects the product ID from "getProductController.py", it retrieves information about the product using the ID it collected. </p><br> + +<p>c. addRevew.py: This collects review information from "addReviewController.py" and inserts into the ReviewsandRatings table of the product database. </p><br> + +<p>d. database_connection.py: This section handles the database connection details for this microservice. </p><br> + + +<h4>3. Config.py:</h4> This file contains the necessary configurations for this microservice - port number, server name. + + +<h4>4. Index.py:</h4> This file manages the routing for this microservice. diff --git a/Product_MicroService_Group3/app.yaml b/Product_MicroService_Group3/app.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ab488dfb9077cfca361f84c8511c86b3ed935dbb --- /dev/null +++ b/Product_MicroService_Group3/app.yaml @@ -0,0 +1,2 @@ +runtime: python312 +entrypoint: gunicorn -b :$PORT index:app \ No newline at end of file diff --git a/Product_MicroService_Group3/app/__init__.py b/Product_MicroService_Group3/app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e5da06c31592e6410ec992cae27f65004ade98d6 --- /dev/null +++ b/Product_MicroService_Group3/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/Product_MicroService_Group3/app/__pycache__/config.cpython-311.pyc b/Product_MicroService_Group3/app/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24ec9fb0533112f07ac3fe8e36eedc6a3f03e748 Binary files /dev/null and b/Product_MicroService_Group3/app/__pycache__/config.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/config.py b/Product_MicroService_Group3/app/config.py new file mode 100644 index 0000000000000000000000000000000000000000..96b983721bde59aa5a40f74aee24fe6a8c0075ff --- /dev/null +++ b/Product_MicroService_Group3/app/config.py @@ -0,0 +1,9 @@ +import os + + +DEBUG = True +SECRET_KEY = "Group3" + +PORT = 5001 + +KAFKA_SERVER= "kafka:9092" \ No newline at end of file diff --git a/Product_MicroService_Group3/app/controllers/__init__.py b/Product_MicroService_Group3/app/controllers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..83fe4b03fbf1ea1097fdec730685170c3a35cc8d --- /dev/null +++ b/Product_MicroService_Group3/app/controllers/__init__.py @@ -0,0 +1,3 @@ +from flask import Flask +from flask import Blueprint + diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/__init__.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a4bfd8103bb4380305718ed59e96e027557a8a4 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/__init__.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/addReviewController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/addReviewController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8ef2134af3385e3f89b7bc21c35f53302104a00 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/addReviewController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/changePasswordController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/changePasswordController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24490b5e77a15d00d5e34c42c5464eadaa725643 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/changePasswordController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f0bf9cd2f33585660325e6ee6fa5211a03cf5c3 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/getProductController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/getProductController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..927290aca63a4c1d8b1ad3a1fa801dae5ce7413d Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/getProductController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/getReviews.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/getReviews.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ccdb50ab470f1f972531278c526c941297e5bd9 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/getReviews.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/getReviewsController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/getReviewsController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c78e2bcbd74045dce7a609453092db0728bf0ef Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/getReviewsController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/login.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee02eac64b23cc7222f821324eb85aa9d76b8dc0 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/login.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/loginController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/loginController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ae33a0fab843db2a609b147bf8a5d6eaea25f24 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/loginController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/logoutController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/logoutController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fef5e24b0dc447c671daa0656ba2d91c90e66b0a Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/logoutController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/productHomeController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/productHomeController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28e57e844880d4812c306d2a88329e736f9193a6 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/productHomeController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/signupController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/signupController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6315f975befa50cae416644dbce66638592358c Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/signupController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/updateProductController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/updateProductController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a851b605b4771ad33e986818477a8d99267d3c2 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/updateProductController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/__pycache__/updateProfileController.cpython-311.pyc b/Product_MicroService_Group3/app/controllers/__pycache__/updateProfileController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99f644e6d21227d53f1b3576205412c1a32bf653 Binary files /dev/null and b/Product_MicroService_Group3/app/controllers/__pycache__/updateProfileController.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/controllers/addReviewController.py b/Product_MicroService_Group3/app/controllers/addReviewController.py new file mode 100644 index 0000000000000000000000000000000000000000..4f85e35e1ded97b201011a4e17ba815135224f7e --- /dev/null +++ b/Product_MicroService_Group3/app/controllers/addReviewController.py @@ -0,0 +1,99 @@ +from flask import Blueprint, jsonify, request, json, session +from models.addReview import add_user_review + +import requests + + +add_review_bp = Blueprint("addReview",__name__) + +#This connects to the user microservice to get username when a user wants to add a review +#It uses a get method to get the product id +@add_review_bp.route("/product/<int:productID>/addReview", methods=["GET"]) +def get_username_from_user_microservice(productID): + + user_id = session.get("user_id") + + if user_id: + if request.method == 'GET': + + + product_id = productID + + response = requests.post('http://localhost:5000/user/getUsername', json={'id': user_id}) + if response.status_code == 200: + username = response.json()['username'] + userID = user_id + session['username'] = username + session['productID'] = product_id + + + rating_info = { + "UserID" : userID, + "ProductID" : product_id, + "Username" : username + } + + return rating_info + else: + return {"Error" : "Failed to retrieve username"} + + #return "You can review the product with ID: {}".format(product_id) + + else: + return {"error" : "null"} + + else: + return {"error" : "You need to be logged in to add a review"} + + + + +@add_review_bp.route("/product/<int:productID>/addReview", methods=["POST"]) +def add_review(productID): + + user_id = session.get("user_id") + + if user_id: + if request.method == 'POST': + + data = request.get_json() + review = data.get("review") + rating = data.get("rating") + product_id = productID + username = session.get('username') + product_id = session.get('productID') + + if review.strip() != "": + + if username is None: + return {"error": "Username is not available"} + + # Check if product_id is available + if product_id is None: + return {"error": "Product ID is not available"} + + if isinstance(rating, int) and 1 <= rating <= 5: + + review_info = { + "UserID" : user_id, + "ProductID" : product_id, + "Review" : review, + "Rating" : rating, + "Username" : username + } + + user_review_message = add_user_review(review_info) + return user_review_message + + else: + return {"error" : "Rating must be an integer between 1 and 5"} + + else: + return {"error" : "Review cannot be empty"} + + + else: + return {"error" : "null"} + + else: + return {"error" : "You need to be logged in to add a review"} \ No newline at end of file diff --git a/Product_MicroService_Group3/app/controllers/getProductController.py b/Product_MicroService_Group3/app/controllers/getProductController.py new file mode 100644 index 0000000000000000000000000000000000000000..88c9f2c6a960befdd847ab73ef534d0bc63b3582 --- /dev/null +++ b/Product_MicroService_Group3/app/controllers/getProductController.py @@ -0,0 +1,30 @@ +from flask import Blueprint, jsonify, request, json, session +from models.getProduct import get_product + + +display_product_bp = Blueprint("product",__name__) + + +@display_product_bp.route("/product/<int:productID>", methods=["GET"]) +def display_product(productID): + + user_id = session.get("user_id") + + + if request.method == 'GET': + + + product_id = productID + + + # Convert to JSON + #json_user_data = json.dumps(user_data) + + product, images, reviews = get_product(product_id) #Send user info to database + customers = [review_data["CustomerID"] for review_data in reviews] + + + return jsonify(customers,{"product" : product, "images" : images, "reviews" : reviews, "session" : user_id}) + + else: + return {"error" : "null"} \ No newline at end of file diff --git a/Product_MicroService_Group3/app/controllers/getReviews.py b/Product_MicroService_Group3/app/controllers/getReviews.py new file mode 100644 index 0000000000000000000000000000000000000000..87f4f38ae4126f9f2fa914984b3e65a4ee33d794 --- /dev/null +++ b/Product_MicroService_Group3/app/controllers/getReviews.py @@ -0,0 +1,44 @@ +from flask import Blueprint, jsonify, request, json, session +from models.getReviews import get_reviews + +from kafka import KafkaProducer + + +get_review_bp = Blueprint("getReview",__name__) + +@get_review_bp.route("/product/getReview", methods=["POST"]) +def get_review(): + + user_id = session.get("user_id") + + if user_id: + if request.method == 'POST': + + #data = request.get_json() + #review = data.get("review") + + get_reviews_by_user = get_reviews(user_id) + + send_review_message(get_reviews_by_user) + + return({"reviews" : get_reviews_by_user}) + + else: + return {"error" : "null"} + + else: + return {"error" : "You need to be logged in to add a review"} + + +producer = KafkaProducer(bootstrap_servers = "localhost:9092") + +def send_review_message(reviews): + metadata =producer.send("customer_reviews", json.dumps(reviews).encode("utf_8")) + try: + record_metadata = metadata.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) \ No newline at end of file diff --git a/Product_MicroService_Group3/app/controllers/getReviewsController.py b/Product_MicroService_Group3/app/controllers/getReviewsController.py new file mode 100644 index 0000000000000000000000000000000000000000..3ecdc45c326f26eba4710d3f4a5ef70e696db4a8 --- /dev/null +++ b/Product_MicroService_Group3/app/controllers/getReviewsController.py @@ -0,0 +1,45 @@ +from flask import Blueprint, jsonify, request, json, session +from models.getReviews import get_reviews +from config import KAFKA_SERVER + +from kafka import KafkaProducer + +#This gets reviews posted by a certain user +get_review_bp = Blueprint("getReview",__name__) + +@get_review_bp.route("/product/getReview", methods=["POST"]) +def get_review(): + + user_id = session.get("user_id") + + if user_id: + if request.method == 'POST': + + #data = request.get_json() + #review = data.get("review") + + get_reviews_by_user = get_reviews(user_id) + + send_review_message(get_reviews_by_user) + + return({"reviews" : get_reviews_by_user}) + + else: + return {"error" : "null"} + + else: + return {"error" : "You need to be logged in to add a review"} + + +producer = KafkaProducer(bootstrap_servers = KAFKA_SERVER) + +def send_review_message(reviews): + metadata =producer.send("customer_reviews", json.dumps(reviews).encode("utf_8")) + try: + record_metadata = metadata.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) \ No newline at end of file diff --git a/Product_MicroService_Group3/app/controllers/productHomeController.py b/Product_MicroService_Group3/app/controllers/productHomeController.py new file mode 100644 index 0000000000000000000000000000000000000000..4acfaf5e5c3babe95574864bb08424664a0ca925 --- /dev/null +++ b/Product_MicroService_Group3/app/controllers/productHomeController.py @@ -0,0 +1,30 @@ +from flask import Blueprint, jsonify, request, json, session +from models.productHome import get_product_by_section + + +product_home_bp = Blueprint("home",__name__) + + +@product_home_bp.route("/product/home", methods=["POST"]) +def product_section(): + + user_id = session.get("user_id") + + + if request.method == 'POST': + + data = request.get_json() + category_id = data.get("category_id") + + + # Convert to JSON + #json_user_data = json.dumps(user_data) + + products = get_product_by_section(category_id) #Send user info to database + #customers = [review_data["CustomerID"] for review_data in reviews] + + + return jsonify({"products" : products, "session" : user_id}) + + else: + return {"error" : "null"} \ No newline at end of file diff --git a/Product_MicroService_Group3/app/controllers/updateProductController.py b/Product_MicroService_Group3/app/controllers/updateProductController.py new file mode 100644 index 0000000000000000000000000000000000000000..70d31a74e24364a5ac7a0d7f9e90ede23b20d0b3 --- /dev/null +++ b/Product_MicroService_Group3/app/controllers/updateProductController.py @@ -0,0 +1,54 @@ +from flask import Blueprint, jsonify, request, json, session +from models.updateProduct import update_product_info +from publishers.kafkaPublishers import publish_product_updated_event + +import requests + + +update_product_bp = Blueprint("updateProduct",__name__) + + +@update_product_bp.route("/product/updateProduct", methods=["POST"]) +def update_product(): + + user_id = session.get("user_id") + + if user_id: + if request.method == 'POST': + + data = request.get_json() + price = data.get("price") + quantity = data.get("quantity") + product_id = data.get("product_id") + #username = session.get('username') + + if isinstance(data.get("price"), (int, float)): + + if isinstance(data.get("quantity"), int): + info = { + "quantity" : quantity, + "price" : price, + "product_id" : product_id + } + + update = update_product_info(info) + if "message" in update: + event_data = {"quantity" : quantity, "price" : price, "product_id" : product_id} + publish_product_updated_event(event_data) + + return {"Update Status": update} + else: + return {"error" : "error"} + else: + + return {"error" : "Quantity should be int"} + else: + return{"error" : "Price should be a number"} + + + + else: + return {"error" : "null"} + + else: + return {"error" : "You need to be logged in to add a review"} \ No newline at end of file diff --git a/Product_MicroService_Group3/app/events/__init__.py b/Product_MicroService_Group3/app/events/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Product_MicroService_Group3/app/index.py b/Product_MicroService_Group3/app/index.py new file mode 100644 index 0000000000000000000000000000000000000000..676b86d8db8a3ab0bfc301378d4f1af0abc75a89 --- /dev/null +++ b/Product_MicroService_Group3/app/index.py @@ -0,0 +1,63 @@ +from flask import Flask, redirect, url_for, request, render_template, make_response, session, abort +from flask_cors import CORS +from flask import jsonify + + +from config import DEBUG, SECRET_KEY, PORT +import os +import requests +import subscribers + +from controllers.getProductController import display_product_bp +from controllers.addReviewController import add_review_bp +from controllers.productHomeController import product_home_bp +from controllers.getReviewsController import get_review_bp +from controllers.updateProductController import update_product_bp + + + + +app = Flask(__name__) +CORS(app) + + +app.secret_key = SECRET_KEY + + +# Read user microservice URL from environment variable +USER_MICROSERVICE_URL = os.getenv('USER_MICROSERVICE_URL', 'http://127.0.0.1:5000') + + +#subscribers.consume_username_updated_event() + +@app.route('/product', methods=['POST']) +def get_session_id(): + session_id = session.get('user_id') + if session_id: + return jsonify({'session_id': session_id}) + else: + return jsonify({'message': 'Session ID not found'}) + + + +@app.route('/') +def index(): + return render_template("index.html") + + + +app.register_blueprint(display_product_bp) +app.register_blueprint(add_review_bp) +app.register_blueprint(product_home_bp) +app.register_blueprint(get_review_bp) +app.register_blueprint(update_product_bp) + + + + + +if __name__ == '__main__': + + subscribers.start_kafka_consumer() + + app.run(debug=DEBUG, port=PORT) \ No newline at end of file diff --git a/Product_MicroService_Group3/app/models/__init__.py b/Product_MicroService_Group3/app/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d55ce4a0c8e0748e87cd3ecae1b6fe12294bdae3 --- /dev/null +++ b/Product_MicroService_Group3/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/Product_MicroService_Group3/app/models/__pycache__/__init__.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfb0366dfbe4ee46a4cd5603bac50bd5dcb0efcf Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/addReview.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/addReview.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e72c77e0f1e3ecc5183d5731a8b0a948337a6a9 Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/addReview.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/changePassword.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/changePassword.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e2f696425d98b4acece898d09560e3b644ec81a Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/changePassword.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/database_connection.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/database_connection.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..005839d68435047d092e0b3a8b7c4d965cb7ff3d Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/database_connection.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/deleteProfile.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/deleteProfile.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11cd5e31a86ac49992cc243db920bc089e02298a Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/deleteProfile.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/getProduct.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/getProduct.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9147d61c3c536e412c3a60130eaf89b7d9581a8 Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/getProduct.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/getReviews.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/getReviews.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ccf1bdd0dc2a3f16681b83ed686dd3e96875123 Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/getReviews.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/login.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54f0eb17fc5e099f4eaf6dcc17f1775c07a610e1 Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/login.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/models.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c5497a917a5f33bfe0edde28b561ef47b6a7caf Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/models.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/productHome.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/productHome.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..491496f7b159eb7864c8ba8eaaddc449f296add3 Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/productHome.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/signup.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/signup.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94a888ae05aa3787387431d65168edf553a0a2cf Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/signup.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/updateProduct.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/updateProduct.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68e89d2ebd24e81165e2c46e12cb0eb9b6280211 Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/updateProduct.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/updateProfile.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/updateProfile.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e15076a9dadaac885919dee4330bc1ada1af79da Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/updateProfile.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/__pycache__/updateUsername.cpython-311.pyc b/Product_MicroService_Group3/app/models/__pycache__/updateUsername.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d55720a3e7872e81ac56a1facc02ba57c682325d Binary files /dev/null and b/Product_MicroService_Group3/app/models/__pycache__/updateUsername.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/models/addReview.py b/Product_MicroService_Group3/app/models/addReview.py new file mode 100644 index 0000000000000000000000000000000000000000..8f67372fa8e1e80281e735675913bf514855c42c --- /dev/null +++ b/Product_MicroService_Group3/app/models/addReview.py @@ -0,0 +1,41 @@ +import pyodbc +from flask import jsonify +from models.database_connection import connect_db + + + +def add_user_review(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + + #insert data into reviews and ratings table + insert_user_query = '''INSERT INTO dbo.ReviewsAndRatings (CustomerID, ProductID, Review, rating, Username) +VALUES (?, ?, ?, ?, ?);''' + cursor.execute(insert_user_query, (data["UserID"], data["ProductID"], data["Review"], data["Rating"], data["Username"])) + + + + #commit changes to database if no errors + connection.commit() + + return {"message" : "Review added successfully"} + + except pyodbc.Error as e: #more error handling + print(f"Database error in add_review: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in add_review: {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/Product_MicroService_Group3/app/models/database_connection.py b/Product_MicroService_Group3/app/models/database_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..0986cea0aafc84365dceb7ca9170ffa9bbcdeb02 --- /dev/null +++ b/Product_MicroService_Group3/app/models/database_connection.py @@ -0,0 +1,14 @@ +import pyodbc + + +#Connect to database +def connect_db(): + + server = 'Chiamaka' + database = 'Products' + username = 'CHIAMAKA\amych' + password = '' + + connection_string = f'DRIVER={{SQL Server}};SERVER={server};DATABASE={database};UID={username};PWD={password};Trusted_Connection=yes;' + + return pyodbc.connect(connection_string) \ No newline at end of file diff --git a/Product_MicroService_Group3/app/models/getProduct.py b/Product_MicroService_Group3/app/models/getProduct.py new file mode 100644 index 0000000000000000000000000000000000000000..e66255704b9cba4288f27ebacfb872656be7d5ab --- /dev/null +++ b/Product_MicroService_Group3/app/models/getProduct.py @@ -0,0 +1,59 @@ +#from app import db +import pyodbc +from flask import jsonify +from models.database_connection import connect_db + +#Function to get user info +def get_product(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + product_id = data + + #Get image info from database + product_query = "SELECT * FROM dbo.ProductCatalog WHERE ProductID = ?" + cursor.execute(product_query, product_id) + + row = cursor.fetchone() #fetch data + + columns = [column[0] for column in cursor.description] + product_data = dict(zip(columns, row)) + + #Collect image information from database + image_query = "SELECT * FROM dbo.ProductImage WHERE ProductID = ?" + cursor.execute(image_query, product_id) + images = cursor.fetchall() + + image_data = [{"ImageIdentifier": row[0], "ProductID": row[1]} for row in images] + + + #Get reviews from database + reviews_query = "SELECT CustomerID, Username, Review, rating FROM dbo.ReviewsAndRatings WHERE ProductID= ?" + cursor.execute(reviews_query, product_id) + reviews = cursor.fetchall() + + reviews_data = [{"CustomerID": row[0], "Username": row[1], "Review": row[2], "rating" : row[3]} for row in reviews] + + + + #connection.close() + + return (product_data, image_data, reviews_data) + + except pyodbc.Error as e: #error handling + print(f"Database error in get_product: {e}") + return None + + except Exception as e: #error handling + print(f"Unexpected error occured in get_product: {e}") + return None + + finally: + if cursor: + cursor.close() + if connection: + connection.close() + \ No newline at end of file diff --git a/Product_MicroService_Group3/app/models/getReviews.py b/Product_MicroService_Group3/app/models/getReviews.py new file mode 100644 index 0000000000000000000000000000000000000000..ce8811ec24a858c8b20354711fd2837a1e8dc0eb --- /dev/null +++ b/Product_MicroService_Group3/app/models/getReviews.py @@ -0,0 +1,42 @@ +#from app import db +import pyodbc +from flask import jsonify +from models.database_connection import connect_db + +#Function to get user info +def get_reviews(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + user_id = data + + #Get reviews from database + reviews_query = "SELECT CustomerID, Username, Review, rating, ProductID FROM dbo.ReviewsAndRatings WHERE CustomerID= ?" + cursor.execute(reviews_query, user_id) + reviews = cursor.fetchall() + + reviews_data = [{"CustomerID": row[0], "Username": row[1], "Review": row[2], "rating" : row[3], "ProductID" : row[4]} for row in reviews] + + + + #connection.close() + + return (reviews_data) + + except pyodbc.Error as e: #error handling + print(f"Database error in get_review: {e}") + return None + + except Exception as e: #error handling + print(f"Unexpected error occured in get_review: {e}") + return None + + finally: + if cursor: + cursor.close() + if connection: + connection.close() + \ No newline at end of file diff --git a/Product_MicroService_Group3/app/models/productHome.py b/Product_MicroService_Group3/app/models/productHome.py new file mode 100644 index 0000000000000000000000000000000000000000..5370a1688ec61d24ae906b532764910788cb2357 --- /dev/null +++ b/Product_MicroService_Group3/app/models/productHome.py @@ -0,0 +1,80 @@ +#from app import db +import pyodbc +from flask import jsonify +from models.database_connection import connect_db + +#Function to get user info +def get_product_by_section(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + category_id = data + + #Get image info from database + product_section_query = """SELECT + t1.ProductID, + t1.ProductName, + t1.ProductDesc, + t1.Price, + t3.ProductCategoryID, + t3.CategoryName, + COALESCE(AVG(t4.Rating), 0) AS AverageRating, + t5.ImageIdentifier +FROM + dbo.ProductCatalog AS t1 +INNER JOIN + dbo.ProductCategory AS t2 ON t1.ProductID = t2.ProductID +INNER JOIN + dbo.Category AS t3 ON t2.ProductCategoryID = t3.ProductCategoryID +LEFT JOIN + dbo.ReviewsAndRatings AS t4 ON t1.ProductID = t4.ProductID +LEFT JOIN + dbo.ProductImage AS t5 ON t1.ProductID = t5.ProductID +WHERE + t3.ProductCategoryID = ? +GROUP BY + t1.ProductID, + t1.ProductName, + t1.ProductDesc, + t1.Price, + t3.ProductCategoryID, + t3.CategoryName, + t5.ImageIdentifier; +""" + + cursor.execute(product_section_query, category_id) + + products = cursor.fetchall()#fetch data + + products_data = [{"ProductID": row[0], + "ProductName": row[1], + "ProductDesc": row[2], + "Price" : row[3], + "ProductCategoryID" : row[4], + "CategoryName" : row[5], + "AverageRating" : row[6], + "ImageName" : row[7]} for row in products] + + + + #connection.close() + + return (products_data) + + except pyodbc.Error as e: #error handling + print(f"Database error in get_product_by_section: {e}") + return None + + except Exception as e: #error handling + print(f"Unexpected error occured in get_product_by_section: {e}") + return None + + finally: + if cursor: + cursor.close() + if connection: + connection.close() + \ No newline at end of file diff --git a/Product_MicroService_Group3/app/models/updateProduct.py b/Product_MicroService_Group3/app/models/updateProduct.py new file mode 100644 index 0000000000000000000000000000000000000000..4ad88367ed92d9a0bcd10de2e7b7cc8eab2053c8 --- /dev/null +++ b/Product_MicroService_Group3/app/models/updateProduct.py @@ -0,0 +1,45 @@ +import pyodbc +from flask import jsonify +from models.database_connection import connect_db + + + +def update_product_info(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + + #insert data into reviews and ratings table + update_product_query = '''UPDATE dbo.ProductCatalog +SET + Price = CASE WHEN ? IS NOT NULL THEN ? ELSE Price END, + StockQuantity = CASE WHEN ? IS NOT NULL THEN ? ELSE StockQuantity END +WHERE + ProductID = ?;''' + cursor.execute(update_product_query, (data["price"], data["price"], data["quantity"], data["quantity"], data["product_id"])) + + + + #commit changes to database if no errors + connection.commit() + + return {"message" : "Product updated successfully"} + + except pyodbc.Error as e: #more error handling + print(f"Database error in add_review: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in add_review: {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/Product_MicroService_Group3/app/models/updateUsername.py b/Product_MicroService_Group3/app/models/updateUsername.py new file mode 100644 index 0000000000000000000000000000000000000000..d673f8bcf96de129c3a5f95196c928bdff094b72 --- /dev/null +++ b/Product_MicroService_Group3/app/models/updateUsername.py @@ -0,0 +1,48 @@ +#from app import db +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + +def update_username(data): + + try: #error handling + + connection = connect_db() + cursor = connection.cursor() + + user_id = data["user_id"] + username = data["new_username"] + + #insert data into user table + update_user_query = '''UPDATE dbo.ReviewsAndRatings + SET + Username= ? + WHERE + CustomerID= ?''' + + cursor.execute(update_user_query, (username, user_id)) + + + #commit changes to database if no errors + connection.commit() + + return {"message" : "Username updated successfully"} + + + + except pyodbc.Error as e: #more error handling + print(f"Database error in update_username: {e}") + connection.rollback() + return {"Error" : "Database error"} + + except Exception as e: #more error handling + print(f"Unexpected error occured in update_username: {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/Product_MicroService_Group3/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc b/Product_MicroService_Group3/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0dc8ca0740bb9e53a5d54a2c55ad96fb36f17dbe Binary files /dev/null and b/Product_MicroService_Group3/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/publishers/kafkaPublishers.py b/Product_MicroService_Group3/app/publishers/kafkaPublishers.py new file mode 100644 index 0000000000000000000000000000000000000000..d03504a620a6397896bf554fed530ac58c48af2e --- /dev/null +++ b/Product_MicroService_Group3/app/publishers/kafkaPublishers.py @@ -0,0 +1,45 @@ +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_product_updated_topic(): + # Create KafkaAdminClient instance + admin_client = KafkaAdminClient(bootstrap_servers=KAFKA_SERVER) + + # Define the topic name and configuration + topic_name = "product_updated_topic" + num_partitions = 1 + replication_factor = 1 + + # Retrieve the list of existing topics + topic_metadata = admin_client.list_topics() + + # Check if the topic already exists + if topic_name not in topic_metadata: + # Define the configuration for the new topic + new_topic = NewTopic(name=topic_name, num_partitions=num_partitions, replication_factor=replication_factor) + # Create the new topic + admin_client.create_topics(new_topics=[new_topic], validate_only=False) + + +#Function is called in updateProfileControllers.py +def publish_product_updated_event(event_data): + # Serialize the event data to JSON + event_json = json.dumps(event_data) + # Publish the event to the Kafka topic + data_to_send = producer.send("product_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/Product_MicroService_Group3/app/run.py b/Product_MicroService_Group3/app/run.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Product_MicroService_Group3/app/subscribers/__init__.py b/Product_MicroService_Group3/app/subscribers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..377b837184694cf1acc74fa09e655f651327ffb0 --- /dev/null +++ b/Product_MicroService_Group3/app/subscribers/__init__.py @@ -0,0 +1,5 @@ +print("Subscribers package initialized") + +from subscribers.updateUsernameSubscriber import consume_username_updated_event + +from subscribers.updateUsernameSubscriber import start_kafka_consumer \ No newline at end of file diff --git a/Product_MicroService_Group3/app/subscribers/__pycache__/__init__.cpython-311.pyc b/Product_MicroService_Group3/app/subscribers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3983da2bfc76957fedcc9a12c59acbeee1d68af6 Binary files /dev/null and b/Product_MicroService_Group3/app/subscribers/__pycache__/__init__.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/subscribers/__pycache__/updateUsernameSubscriber.cpython-311.pyc b/Product_MicroService_Group3/app/subscribers/__pycache__/updateUsernameSubscriber.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecca0f3a049b16bf2c1abebacb308cdcb80d4cc0 Binary files /dev/null and b/Product_MicroService_Group3/app/subscribers/__pycache__/updateUsernameSubscriber.cpython-311.pyc differ diff --git a/Product_MicroService_Group3/app/subscribers/updateUsernameSubscriber.py b/Product_MicroService_Group3/app/subscribers/updateUsernameSubscriber.py new file mode 100644 index 0000000000000000000000000000000000000000..4235a17f9f2c3b3673d1cc139fc1ebf9cb9001d4 --- /dev/null +++ b/Product_MicroService_Group3/app/subscribers/updateUsernameSubscriber.py @@ -0,0 +1,34 @@ +from kafka import KafkaConsumer + +from config import KAFKA_SERVER + +from models.updateUsername import update_username + +from threading import Thread + + +import json +import logging + +#This function is called in the "__init__.py" file in the "subscribers" folder +def consume_username_updated_event(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + logging.info("Consuming username updated event...") + consumer = KafkaConsumer("profile_updated_topic", bootstrap_servers=KAFKA_SERVER) + for message in consumer: + profile_data_str = message.value.decode("utf-8") + profile_data = json.loads(profile_data_str) + print("I am here") + print(profile_data) + update_username(profile_data) + +def start_kafka_consumer(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + logging.info("Starting Kafka consumer...") + print("Hello from kafka consumer") + kafka_thread = Thread(target=consume_username_updated_event) + kafka_thread.daemon = True # Daemonize the thread so it will be automatically killed when the main thread exits + kafka_thread.start() + +# Call the start_kafka_consumer function to start the Kafka consumer thread + \ No newline at end of file diff --git a/Product_MicroService_Group3/app/templates/index.html b/Product_MicroService_Group3/app/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..6207c5207b735653b32e6ad59ac7d969cdfab4eb --- /dev/null +++ b/Product_MicroService_Group3/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/Product_MicroService_Group3/requirements.txt b/Product_MicroService_Group3/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..aed8ace483f880061abeee73b97954403c1890b1 --- /dev/null +++ b/Product_MicroService_Group3/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 diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a6f23f1b57de19d54d2fa06a5055731e75ddf4a5 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# Advanced Web Project + + + +## Getting started + +To make it easy for you to get started with GitLab, here's a list of recommended next steps. + +Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! + +## Add your files + +- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files +- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: + +``` +cd existing_repo +git remote add origin https://gitlab.surrey.ac.uk/fc00617/advanced-web-project.git +git branch -M main +git push -uf origin main +``` + +## Integrate with your tools + +- [ ] [Set up project integrations](https://gitlab.surrey.ac.uk/fc00617/advanced-web-project/-/settings/integrations) + +## Collaborate with your team + +- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) +- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) +- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) +- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) +- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) + +## Test and Deploy + +Use the built-in continuous integration in GitLab. + +- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) +- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) +- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) +- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) +- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) + +*** + +# Editing this README + +When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. + +## Suggestions for a good README + +Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. + +## Name +Choose a self-explaining name for your project. + +## Description +Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. + +## Badges +On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. + +## Visuals +Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. + +## Installation +Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. + +## Usage +Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. + +## Support +Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. + +## Roadmap +If you have ideas for releases in the future, it is a good idea to list them in the README. + +## Contributing +State if you are open to contributions and what your requirements are for accepting them. + +For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. + +You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. + +## Authors and acknowledgment +Show your appreciation to those who have contributed to the project. + +## License +For open source projects, say how it is licensed. + +## Project status +If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. diff --git a/User_MicroService_Group3/Dockerfile b/User_MicroService_Group3/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..3e7d584f9207328c2b4ae5b319bc506fcd1394da --- /dev/null +++ b/User_MicroService_Group3/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.11.4 + +COPY requirements.txt /user_ms/ + +COPY app /user_ms/ + +WORKDIR /user_ms + +RUN pip install --no-cache-dir -r requirements.txt + + +RUN apt-get update && apt-get install -y \ + unixodbc \ + unixodbc-dev \ + freetds-dev \ + tdsodbc \ + && rm -rf /var/lib/apt/lists/* + +# 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_MicroService_Group3/README.md b/User_MicroService_Group3/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d2484075b7529977898561ff66a1fbc253435f2f --- /dev/null +++ b/User_MicroService_Group3/README.md @@ -0,0 +1,38 @@ +#ReadMe + +<h2>User Microservice</h2> + +<h3>Folders and Files in the User Microservice</h3> + +<h5>1. Controllers/:</h5><br> +<p>a. signupController.py: This page collects information from the user when they log sign up. It uses a POST request method. The user's password is encoded using the hashlib and secrets libraries. If no email address is entered, it does not allow sign up. Because email is set to not null in the database.</p><br> + +<p>b. loginController.py: This page handles user log in and user authentication. It uses a POST request method to collect the user's email address and password. If an email is entered that does not exist, it throws an error to the user. If the email exists in the database, it then checks the entered password. The password is encoded and compared with the encoded password in the database. If it matches, the sign in is successful. A session is then created and is accessible from the server.</p><br> + +<p>c. logoutController.py: This page handles the logout function and uses a POST request. It first of all checks if the session is active. If it is, it is deleted from the server.</p><br> + +<p>d. updateProfileController.py: This section handles changes made to the user's personal information. It uses a POST request method to get the updated information from the user. It does not update the email address because this is constant. It checks if the user's session is active. If it is active, it sends the updated information to the database using the session data. If the session is not active, it throws an error message to the user to log in.</p><br> + +<p>e. changePasswordController.py: This section handles changing the user's password. It is collected from the user using a POST request. It checks if the user's session is active. If it isn't, it throws an error for the user to log in. If it is, the user's old and new password are collected. The old password is checked against the database. If it matches, the new password is then decoded and updated in the database.</p><br> + +<p>f. deleteProfileController.py: This section handles deleting a user's profile. It checks if the user's session is active. If it isn't, it throws an error for the user to log in. If the session is active, it calls the database function to delete the user's profile.</p><br> + +<h5>2. Models/:</h5><br> +<p>a. sigup.py: This collects the user's information from "signupController.py" and checks if the email exists in the database. If the email does not exist, it inserts the user's data in the database.</p><br> + +<p>b. login.py: This page collects log in information from "loginController.py". It has 2 functions. The first function retrieves the email the user entered if it exists. The second function collects the password and sends it to the "loginController.py" page for verification.</p><br> + +<p>c. updateProfile.py: This page collects the user's information from "updateProfileController.py" and inserts it into the database.</p><br> + +<p>d. changePassword.py: This page collects password information from "changePasswordController.py". It has 2 functions. The first function gets the old password, and the second function sets the new password. The second function is called only if the old password matches the password in the database.</p><br> + +<p>e. deleteProfile.py: This section collects the user's ID from "deleteProfileController.py" and deletes the user's profile if the profile exists.</p><br> + +<p>f. Database_connection.py: This section handles the database connection details for this microservice.</p><br> + +<p>All database codes use the try statement for error handling and rolls back changes if not all database operations were successful in a given function.</p><br> + +<h5>3. Config.py:</h5><p> This file contains the necessary configurations for this microservice - port number, server name</p> + +<h5>4. Index.py:</h5><p> This file manages the routing for this microservice</p> + diff --git a/User_MicroService_Group3/app.yaml b/User_MicroService_Group3/app.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ab488dfb9077cfca361f84c8511c86b3ed935dbb --- /dev/null +++ b/User_MicroService_Group3/app.yaml @@ -0,0 +1,2 @@ +runtime: python312 +entrypoint: gunicorn -b :$PORT index:app \ No newline at end of file diff --git a/User_MicroService_Group3/app/__init__.py b/User_MicroService_Group3/app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e5da06c31592e6410ec992cae27f65004ade98d6 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/__pycache__/config.cpython-311.pyc b/User_MicroService_Group3/app/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..105819a92eef07108a2fb4558af6e0b1ad972654 Binary files /dev/null and b/User_MicroService_Group3/app/__pycache__/config.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/config.py b/User_MicroService_Group3/app/config.py new file mode 100644 index 0000000000000000000000000000000000000000..3217efec4a6a96ac59bc14a1aef3f74470c83e1a --- /dev/null +++ b/User_MicroService_Group3/app/config.py @@ -0,0 +1,9 @@ +import os + + +DEBUG = True +SECRET_KEY = "Group3" + +PORT = 5000 + +KAFKA_SERVER = "kafka:9092" \ No newline at end of file diff --git a/User_MicroService_Group3/app/controllers/__init__.py b/User_MicroService_Group3/app/controllers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..83fe4b03fbf1ea1097fdec730685170c3a35cc8d --- /dev/null +++ b/User_MicroService_Group3/app/controllers/__init__.py @@ -0,0 +1,3 @@ +from flask import Flask +from flask import Blueprint + diff --git a/User_MicroService_Group3/app/controllers/__pycache__/__init__.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a4bfd8103bb4380305718ed59e96e027557a8a4 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/__init__.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/changePasswordController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/changePasswordController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34cc426ee500d1191ecb52b66c1c4517c902dad9 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/changePasswordController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ce448acfcb8142341af9ce1dbd5583428925b08 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/deleteProfileController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/fetchUsernameController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/fetchUsernameController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cbe6aa45038b2bd7bda92392953d3e3b023f3a3 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/fetchUsernameController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/login.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee02eac64b23cc7222f821324eb85aa9d76b8dc0 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/login.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/loginController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/loginController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0b17d9c4604c96ba994a28b64fd1eebc7061a71 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/loginController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/logoutController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/logoutController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63d12e36ae470889c341e2c26608db4c1b994b39 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/logoutController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/showReviewsController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/showReviewsController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d12a61ce3d85861ca4a71b737865e07616283d1c Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/showReviewsController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/signupController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/signupController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55cd6b76556ac83d7c5d814dc2a64f828492b907 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/signupController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/__pycache__/updateProfileController.cpython-311.pyc b/User_MicroService_Group3/app/controllers/__pycache__/updateProfileController.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b7bc1569e5e102cdf2927d1001d7ef46f330135 Binary files /dev/null and b/User_MicroService_Group3/app/controllers/__pycache__/updateProfileController.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/controllers/changePasswordController.py b/User_MicroService_Group3/app/controllers/changePasswordController.py new file mode 100644 index 0000000000000000000000000000000000000000..5fbb96dbca89b737017962cde091c6c2b72d7499 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/controllers/deleteProfileController.py b/User_MicroService_Group3/app/controllers/deleteProfileController.py new file mode 100644 index 0000000000000000000000000000000000000000..f29f3689923437d63695693ebaf81f1be31a2dcb --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/controllers/fetchUsernameController.py b/User_MicroService_Group3/app/controllers/fetchUsernameController.py new file mode 100644 index 0000000000000000000000000000000000000000..00380458a388542e5aaa69a20561324d7f152a0c --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/controllers/getUsersController.py b/User_MicroService_Group3/app/controllers/getUsersController.py new file mode 100644 index 0000000000000000000000000000000000000000..e2a9bd4c09b48d0beb756dddd03e66562435a8ba --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/controllers/loginController.py b/User_MicroService_Group3/app/controllers/loginController.py new file mode 100644 index 0000000000000000000000000000000000000000..232350cda6d673061fd1a75a26a98ddcd07ab8de --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/controllers/logoutController.py b/User_MicroService_Group3/app/controllers/logoutController.py new file mode 100644 index 0000000000000000000000000000000000000000..241e00d337582cf50f06867c0fb7aeb36c401486 --- /dev/null +++ b/User_MicroService_Group3/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 logges 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/User_MicroService_Group3/app/controllers/showReviewsController.py b/User_MicroService_Group3/app/controllers/showReviewsController.py new file mode 100644 index 0000000000000000000000000000000000000000..924ede66f969d1ac4ce0e76b9d1f78e2de506249 --- /dev/null +++ b/User_MicroService_Group3/app/controllers/showReviewsController.py @@ -0,0 +1,55 @@ +from flask import Blueprint, jsonify, request, session +from kafka import KafkaConsumer +import json +from config import KAFKA_SERVER + +show_reviews_bp = Blueprint("showReviews", __name__) + +# Kafka consumer configuration +consumer_conf = { + "bootstrap_servers": KAFKA_SERVER, + "group_id": "show_reviews_group", # Specify a unique group ID for this consumer + "auto_offset_reset": "earliest" # Start consuming from the beginning of the topic +} + +# Function to consume reviews published by product microservice +def consume_reviews(num_reviews=1): + consumer = KafkaConsumer("customer_reviews", **consumer_conf) + reviews = [] + + # Iterate over messages received by the consumer + for message in consumer: + # Decode the message from bytes to a string using UTF-8 encoding + review_data_str = message.value.decode("utf-8") + + # Parse the JSON-encoded message to extract the review data + review_data = json.loads(review_data_str) + + # Append the review data to the list of reviews + reviews.append(review_data) + + # Exit the loop after consuming the specified number of reviews + if len(reviews) >= num_reviews: + break + + # Close the consumer to release resources + 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/User_MicroService_Group3/app/controllers/signupController.py b/User_MicroService_Group3/app/controllers/signupController.py new file mode 100644 index 0000000000000000000000000000000000000000..0ec066371a3283032f8f059a08a2e7eab3ea1d78 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/controllers/updateProfileController.py b/User_MicroService_Group3/app/controllers/updateProfileController.py new file mode 100644 index 0000000000000000000000000000000000000000..bca3119885f245ad049dc58820fc7c92a1d5aceb --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/events/__init__.py b/User_MicroService_Group3/app/events/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/User_MicroService_Group3/app/events/__pycache__/__init__.cpython-311.pyc b/User_MicroService_Group3/app/events/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95be18671d60d24a8dd5c89660bea97b034080d2 Binary files /dev/null and b/User_MicroService_Group3/app/events/__pycache__/__init__.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/events/__pycache__/eventDefinitions.cpython-311.pyc b/User_MicroService_Group3/app/events/__pycache__/eventDefinitions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26189d001a07adbc3c51f25578fd46235d1830b5 Binary files /dev/null and b/User_MicroService_Group3/app/events/__pycache__/eventDefinitions.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/events/eventDefinitions.py b/User_MicroService_Group3/app/events/eventDefinitions.py new file mode 100644 index 0000000000000000000000000000000000000000..bb88066408e7c588d1eeadaec115cac9b4c2f06a --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/index.py b/User_MicroService_Group3/app/index.py new file mode 100644 index 0000000000000000000000000000000000000000..a22a973629796e6f93ba702e7f1c7aeeb60947fb --- /dev/null +++ b/User_MicroService_Group3/app/index.py @@ -0,0 +1,64 @@ +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 +import os +import requests +import publishers + + + +app = Flask(__name__) +CORS(app) + +app.secret_key = SECRET_KEY + +@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() + + + + +@app.route("/userIDs", methods=["POST"]) +def userIDs(): + + #ids = request.json + #print(ids) + return 'hi' + +if __name__ == '__main__': + app.run(debug=DEBUG, port=PORT) \ No newline at end of file diff --git a/User_MicroService_Group3/app/models/__init__.py b/User_MicroService_Group3/app/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d55ce4a0c8e0748e87cd3ecae1b6fe12294bdae3 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/models/__pycache__/__init__.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfb0366dfbe4ee46a4cd5603bac50bd5dcb0efcf Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/changePassword.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/changePassword.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efbc1602a81cf43627230b17ff6be9b650f9cba8 Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/changePassword.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/database_connection.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/database_connection.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee658c793e4a505d7b70e54b3c82b148e4f6561e Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/database_connection.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/deleteProfile.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/deleteProfile.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11cd5e31a86ac49992cc243db920bc089e02298a Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/deleteProfile.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/fetchUsername.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/fetchUsername.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae95206848ec046c1557589c2b224c793de637e2 Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/fetchUsername.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/getUsers.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/getUsers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd54bdac2b3189ffb26e70c80dcb6d7a9d4b3f61 Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/getUsers.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/login.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54f0eb17fc5e099f4eaf6dcc17f1775c07a610e1 Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/login.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/models.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c5497a917a5f33bfe0edde28b561ef47b6a7caf Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/models.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/signup.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/signup.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94a888ae05aa3787387431d65168edf553a0a2cf Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/signup.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/__pycache__/updateProfile.cpython-311.pyc b/User_MicroService_Group3/app/models/__pycache__/updateProfile.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3cc094c4a2bb3f0c99ed37c439242aab85aaf1a Binary files /dev/null and b/User_MicroService_Group3/app/models/__pycache__/updateProfile.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/models/changePassword.py b/User_MicroService_Group3/app/models/changePassword.py new file mode 100644 index 0000000000000000000000000000000000000000..7cb162b6b303c4a857ba5c7e398741dd48aed1f3 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/models/database_connection.py b/User_MicroService_Group3/app/models/database_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..8bad5678aab1e99f1324fa173b69a9a281ee1c74 --- /dev/null +++ b/User_MicroService_Group3/app/models/database_connection.py @@ -0,0 +1,14 @@ +import pyodbc + + +#Connect to database +def connect_db(): + + server = 'Chiamaka' + database = 'User_Management' + username = 'CHIAMAKA\amych' + password = '' + + connection_string = f'DRIVER={{SQL Server}};SERVER={server};DATABASE={database};UID={username};PWD={password};Trusted_Connection=yes;' + + return pyodbc.connect(connection_string) \ No newline at end of file diff --git a/User_MicroService_Group3/app/models/deleteProfile.py b/User_MicroService_Group3/app/models/deleteProfile.py new file mode 100644 index 0000000000000000000000000000000000000000..7972b969a1ba2a46cee56c5a76c0310f2be1b219 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/models/fetchUsername.py b/User_MicroService_Group3/app/models/fetchUsername.py new file mode 100644 index 0000000000000000000000000000000000000000..437cac41ebc8b863c7bae101dfa15e2b60092046 --- /dev/null +++ b/User_MicroService_Group3/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] # Assuming the username is in the first column + 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 fetch_user_info: {e}") + return None + + finally: + if cursor: + cursor.close() + if connection: + connection.close() diff --git a/User_MicroService_Group3/app/models/getUsers.py b/User_MicroService_Group3/app/models/getUsers.py new file mode 100644 index 0000000000000000000000000000000000000000..f056e2dea7fe5579b85688e91003cd420b0dc591 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/models/login.py b/User_MicroService_Group3/app/models/login.py new file mode 100644 index 0000000000000000000000000000000000000000..6e344a768e8205c00e6d40e93df07c27e1deb941 --- /dev/null +++ b/User_MicroService_Group3/app/models/login.py @@ -0,0 +1,80 @@ +#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 + + 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 + + finally: + if cursor: + cursor.close() + if connection: + connection.close() + + + + + +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 + + finally: + if cursor: + cursor.close() + if connection: + connection.close() \ No newline at end of file diff --git a/User_MicroService_Group3/app/models/signup.py b/User_MicroService_Group3/app/models/signup.py new file mode 100644 index 0000000000000000000000000000000000000000..714741338ac7dfc19add5029d5813f105dbeae6d --- /dev/null +++ b/User_MicroService_Group3/app/models/signup.py @@ -0,0 +1,69 @@ +#from app import db +from flask import jsonify +import pyodbc +from models.database_connection import connect_db + + + +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/User_MicroService_Group3/app/models/updateProfile.py b/User_MicroService_Group3/app/models/updateProfile.py new file mode 100644 index 0000000000000000000000000000000000000000..0c70bedb121f72626f28135dc13b5d22fd962f08 --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/app/publishers/__init__.py b/User_MicroService_Group3/app/publishers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..beb65e5ef1e4ca89b1eb1f7148c7aa845f8a7348 --- /dev/null +++ b/User_MicroService_Group3/app/publishers/__init__.py @@ -0,0 +1 @@ +from publishers.kafkaPublishers import create_profile_updated_topic \ No newline at end of file diff --git a/User_MicroService_Group3/app/publishers/__pycache__/__init__.cpython-311.pyc b/User_MicroService_Group3/app/publishers/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e5ad7960c3085c3269be00737e82ddc12a6c966 Binary files /dev/null and b/User_MicroService_Group3/app/publishers/__pycache__/__init__.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc b/User_MicroService_Group3/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f298ced069dad7e7a1242f27879c71831e59724d Binary files /dev/null and b/User_MicroService_Group3/app/publishers/__pycache__/kafkaPublishers.cpython-311.pyc differ diff --git a/User_MicroService_Group3/app/publishers/kafkaPublishers.py b/User_MicroService_Group3/app/publishers/kafkaPublishers.py new file mode 100644 index 0000000000000000000000000000000000000000..7d41460ea71816937142c157fa6180cc964b4c25 --- /dev/null +++ b/User_MicroService_Group3/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(): + # Create KafkaAdminClient instance + admin_client = KafkaAdminClient(bootstrap_servers=KAFKA_SERVER) + + # Define the topic name and configuration + topic_name = "profile_updated_topic" + num_partitions = 1 + replication_factor = 1 + + # Retrieve the list of existing topics + topic_metadata = admin_client.list_topics() + + # Check if the topic already exists + if topic_name not in topic_metadata: + # Define the configuration for the new topic + new_topic = NewTopic(name=topic_name, num_partitions=num_partitions, replication_factor=replication_factor) + # Create the new topic + 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): + # Serialize the event data to JSON + 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/User_MicroService_Group3/app/run.py b/User_MicroService_Group3/app/run.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/User_MicroService_Group3/app/templates/index.html b/User_MicroService_Group3/app/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..6207c5207b735653b32e6ad59ac7d969cdfab4eb --- /dev/null +++ b/User_MicroService_Group3/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/User_MicroService_Group3/requirements.txt b/User_MicroService_Group3/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..aed8ace483f880061abeee73b97954403c1890b1 --- /dev/null +++ b/User_MicroService_Group3/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