From cd8e4a1d15a8fc773a9c3201b9725a5c7d8f6d41 Mon Sep 17 00:00:00 2001
From: "Marco Emilio \"sphakka\" Poleggi" <marcoep@ieee.org>
Date: Sat, 14 Dec 2024 19:54:19 +0100
Subject: [PATCH] Sync with solution repo

Signed-off-by: Marco Emilio "sphakka" Poleggi <marcoep@ieee.org>
---
 Application/backend/main.py               | 101 +++++++++++++++-------
 Application/frontend/views/dashboard.html |   6 +-
 Application/frontend/views/login.html     |   7 +-
 Application/frontend/views/signup.html    |   2 +-
 Makefile                                  |  14 +--
 5 files changed, 88 insertions(+), 42 deletions(-)

diff --git a/Application/backend/main.py b/Application/backend/main.py
index 35e954a..84c54df 100644
--- a/Application/backend/main.py
+++ b/Application/backend/main.py
@@ -1,19 +1,29 @@
-# Flask Server to serve the frontend pages
+# Flask Server to serve the frontend pages. Ensure that the Flask server is
+# CORS enabled.
+
 from flask import Flask, request, jsonify
 from flask_cors import CORS
 import boto3
-# import json
 import logging
 from functools import wraps
 from dotenv import load_dotenv
 import os
-from datetime import datetime  # Added for timestamps
+import json
+from datetime import datetime
+import time
 
 # Load environment variables
 load_dotenv(dotenv_path='.env')
 
-# Basic logging setup
-logging.basicConfig(level=logging.INFO)
+# Configure logging
+logging.basicConfig(
+    level=logging.INFO,
+    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+    handlers=[
+        logging.FileHandler('app.log'),
+        logging.StreamHandler()
+    ]
+)
 logger = logging.getLogger(__name__)
 
 # Add error handling decorator
@@ -34,8 +44,14 @@ app = Flask(__name__)
 CORS(app)
 
 # Initialize S3 client
-# TODO: Add your S3 credentials as ENV variables or in file
-# '~/.aws/credentials'
+# @TODO: Pass the following variables through the environment:
+#   S3_BUCKET_NAME
+#   SWITCH_ENDPOINT_URL
+#   SWITCH_ACCESS_KEY_ID
+#   SWITCH_SECRET_ACCESS_KEY
+# Tips:
+#   Docker: use a .env file or CLI options --env, --env-file
+#   K8s: use a ConfigMap file and configMapRef
 s3_client = boto3.client(
     's3',
     aws_access_key_id=os.getenv('SWITCH_ACCESS_KEY_ID'),
@@ -65,19 +81,35 @@ def enroll():
 
     if not email or not password:
         logger.warning(f"Enrollment attempt with missing credentials: {data}")
-        return jsonify({'status': 'KO:Missing credentials'}), 400
+        return jsonify({'status': 'KO:MISSING_CREDENTIALS'}), 400
 
-    # TODO: Implement enrollment logic
-    # 1. Check if user already exists
-    # 2. Create new enrollment with timestamp
     enrollment_data = {
         'email': email,
-        'password': password,  # TODO: Hash this password in production
-        'enrolled_at': datetime.utcnow().isoformat()  # Added timestamp
+        'password': password,  # @BONUS: Hash the password
+        'timestamp': time.time()
     }
+    logger.info(
+        f"Enrollment time: {datetime.fromtimestamp(enrollment_data['timestamp']).isoformat()}"
+    )
+
+    # @TODO: Implement enrollment logic
+
+    logger.info(f"Successfully enrolled user: {email}")
+    return jsonify({'status': 'OK:ENROLLED'}), 200
+
+
+# @TODO: Complete this method
+@app.route('/unenroll', methods=['POST'])
+def unenroll():
+    """
+    Endpoint to handle user unenrollment
+    Expected JSON payload: {'email': 'user@example.com'}
+              or ( @BONUS) {'email': 'user@example.com', 'password': 'userpassword'}
+    """
+    pass
 
-    return jsonify({'status': 'OK'}), 200
 
+# @TODO: Complete this method
 @app.route('/login', methods=['POST'])
 @handle_aws_errors
 def login():
@@ -90,21 +122,24 @@ def login():
     password = data.get('password')
 
     if not email:
-        return jsonify({'status': 'KO:Missing email'}), 400
+        logger.warning(f"Login attempt with missing email: {data}")
+        return jsonify({'status': 'KO:MISSING_EMAIL'}), 400
 
-    # TODO: Implement login logic
-    # 1. Check if user exists
-    # 2. Verify password
-    # 3. Create or verify session
+    logger.info(f"Login attempt for email: {email}")
+
+    # @TODO: Implement login logic
     session_data = {
         'email': email,
-        'active': True,
-        'login_at': datetime.utcnow().isoformat()  # Added timestamp
+        'timestamp': time.time()
     }
+    logger.info(
+        f"Enrollment time: {datetime.fromtimestamp(session_data['timestamp']).isoformat()}"
+    )
+
+    return jsonify({'status': 'LOGGED_IN'}), 200
 
-    return jsonify({'status': 'OK'}), 200
 
-# TODO: Implement these endpoints
+# @TODO: Complete this method
 @app.route('/logout', methods=['POST'])
 def logout():
     """
@@ -113,14 +148,18 @@ def logout():
     """
     pass
 
-@app.route('/unenroll', methods=['POST'])
-def unenroll():
-    """
-    Endpoint to handle user unenrollment
-    Expected JSON payload: {'email': 'user@example.com'}
-               or (bonus): {'email': 'user@example.com', 'password': 'userpassword'}
-    """
-    pass
+
+# Handle common HTTP errors
+@app.errorhandler(404)
+def not_found_error(error):
+    logger.error(f"404 error: {error}")
+    return jsonify({'status': 'KO', 'message': 'Resource not found'}), 404
+
+@app.errorhandler(500)
+def internal_error(error):
+    logger.error(f"500 error: {error}")
+    return jsonify({'status': 'KO', 'message': 'Internal server error'}), 500
+
 
 if __name__ == '__main__':
     try:
diff --git a/Application/frontend/views/dashboard.html b/Application/frontend/views/dashboard.html
index 456c2fe..5a231c8 100644
--- a/Application/frontend/views/dashboard.html
+++ b/Application/frontend/views/dashboard.html
@@ -65,7 +65,8 @@
                  });
                  const data = await response.json();
 
-                 if (data.status === 'OK') {
+                 if (data.status === 'OK:LOGGED_OUT') {
+					 alert('You have been logged out.');
                      localStorage.removeItem('userEmail');
                      window.location.href = 'index.html';
                  } else {
@@ -86,7 +87,8 @@
                  });
                  const data = await response.json();
 
-                 if (data.status === 'OK') {
+                 if (data.status === 'OK:UNENROLLED') {
+					 alert('You have been unsubscribed.');
                      localStorage.removeItem('userEmail');
                      window.location.href = 'index.html';
                  } else {
diff --git a/Application/frontend/views/login.html b/Application/frontend/views/login.html
index c5e5098..e9f6d8c 100644
--- a/Application/frontend/views/login.html
+++ b/Application/frontend/views/login.html
@@ -88,10 +88,13 @@
                  });
                  const data = await response.json();
 
-                 if (data.status === 'OK') {
+                 if (data.status === 'OK:LOGGED_IN') {
                      localStorage.setItem('userEmail', email);
                      window.location.href = 'dashboard.html';
-                 } else {
+                 } else if (data.status === 'OK:NEED_PASSWORD') {
+					 localStorage.setItem('userEmail', email);
+					 alert('Please provide your password');
+				 } else {
                      alert('Login failed: ' + data.status);
                  }
              } catch (error) {
diff --git a/Application/frontend/views/signup.html b/Application/frontend/views/signup.html
index a0b5aa8..c6eb4c4 100644
--- a/Application/frontend/views/signup.html
+++ b/Application/frontend/views/signup.html
@@ -88,7 +88,7 @@
                  });
                  const data = await response.json();
 
-                 if (data.status === 'OK') {
+                 if (data.status === 'OK:ENROLLED') {
                      alert('Sign up successful! Please login.');
                      window.location.href = 'login.html';
                  } else {
diff --git a/Makefile b/Makefile
index 2663054..56938ae 100644
--- a/Makefile
+++ b/Makefile
@@ -122,7 +122,7 @@ _venv: _sandbox
 	python -m venv $(sandbox_dir) || exit 1
 
 _install: _sandbox
-	rsync -Cabhv -L --suffix=.bak $(app_sdir) $(sandbox_dir) || exit 1
+	rsync -Cabhv --copy-links $(app_sdir) $(sandbox_dir) || exit 1
 	for comp in $(app_components); do
 		$(log-info) "$${comp}: installing app component's dependencies..."
 		pip -q install $(pip_opts) $(application_dir)/$${comp}/requirements.txt ||
@@ -218,29 +218,31 @@ _danyop_by_cid:
 	done
 
 # Docker generic operation $dop loop by container name
+_op_tag := '(unknown operation)'
 _danyop_by_cname:
 	[ "$(dop)" ] ||  {
 		$(log-error) "No docker operation specified"
 		exit 1
 	}
 	for comp in $(app_components); do
+		$(log-info) "$(_op_tag): $${comp}:"
 		docker $(dop) $(docker_bcname)-$${comp}
 	done
 
 drm:
-	$(MAKE) dop=rm _danyop_by_cname
+	$(MAKE) dop=rm _op_tag='rm' _danyop_by_cname
 
 dstart:
-	$(MAKE) dop=start _danyop_by_cname
+	$(MAKE) dop=start _op_tag='start' _danyop_by_cname
 
 dstop:
-	$(MAKE) dop=stop _danyop_by_cname
+	$(MAKE) dop=stop _op_tag='stop' _danyop_by_cname
 
 dstatus:
 	docker ps -a --filter="name=$(docker_bcname)"
 	$(echoe) "IP ADDRESSES"
 	$(MAKE) dop="inspect --format='{{.NetworkSettings.IPAddress}} {{.Name}}'" \
-		_danyop_by_cname
+		_op_tag='inspect' _danyop_by_cname
 
 dlogs: _check_term
 	for comp in $(app_components); do
@@ -250,7 +252,7 @@ dlogs: _check_term
 	done
 
 drestart:
-	$(MAKE) dop=restart _danyop_by_cname
+	$(MAKE) dop=restart _op_tag='restart' _danyop_by_cname
 
 help:
 	@: $(info $(_help_msg))
-- 
GitLab