diff --git a/Application/backend/main.py b/Application/backend/main.py index 35e954aa45185de10c64bd1abd398955eaa39ccd..84c54df79fde52fac863ca978f185d86617b5f02 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 456c2fe261d4b5b49973bc066717819eb28c31c5..5a231c8fe63e1633d1038358ae9a041644991da1 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 c5e5098a9a09e15398e5dd547e3effda148f20d7..e9f6d8c04e1434af6a385f8c0d115722186a1a96 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 a0b5aa88961c1edbf8c760899933a4c77236c842..c6eb4c478314b5ca9c64e855bf5e7f68f58583e8 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 2663054b5f915a61e219782bd86deab626dd5103..56938aeb13e7ca2ea9b29a5b9370e1fe4ef979e9 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))