diff --git a/store/models.py b/store/models.py index 145f1490b34ea0ebb40ea797688b3e49122b6e96..3857678c4b758e10a2737e8dbd44b71a690acd4a 100644 --- a/store/models.py +++ b/store/models.py @@ -2,6 +2,9 @@ #from flask import current_app from store import db +from flask_login import UserMixin +from werkzeug.security import generate_password_hash, check_password_hash + # Helper tables for many to many relationships, see https://flask-sqlalchemy.palletsprojects.com/en/2.x/models/#many-to-many-relationships. items = db.Table('Items', @@ -47,13 +50,45 @@ class ItemSet(db.Model): # database for user register it will be added more cullomns in the future # that is a basic implementation to check if it works # Freddy implemented that -class User(db.Model): - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(256), nullable=False) - password = db.Column(db.String(256), nullable=False) +#UserMixin is to inherit flask-login implementetion +class User( db.Model, UserMixin): + user_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + username = db.Column(db.String(256), nullable=False, unique=True) + email = db.Column(db.String(256), nullable=False) + phone_number = db.Column(db.Integer, nullable=False) + password_hash = db.Column(db.String(256), nullable=False) + @classmethod - def insert(cls, username, password): - user = cls(username=username, password=password) + def create_user(cls, username: str, password: str,email: str , phone_number: int ) : + password_hash = generate_password_hash(password) + user = cls(username=username, password_hash= password_hash, email=email, phone_number=phone_number) db.session.add(user) - db \ No newline at end of file + db.session.commit() + return user + + @classmethod + def update_passwords(cls, user_id: int, password: str): + user = cls.query.get(user_id) + password_hash = generate_password_hash(password) + user.password_hash = password_hash + db.session.commit() + + + def is_active(self) -> bool: + return True + + + def get_userDetails(self): + lis = [self.username, self.email, self.phone_number] + return lis + + + def get_id(self): + return str(self.user_id) + + def check_password(self, password: str) -> bool: + return check_password_hash(self.password_hash, password) + + def __repr__(self) -> str: + return f"User(user_id={self.user_id}, username='{self.username}')" \ No newline at end of file diff --git a/store/routes.py b/store/routes.py index abebd486d4f7355f7f6924785e5aa9ddc1790242..10dcbcaa83603c0f4c84a0e93d087953d7e02594 100644 --- a/store/routes.py +++ b/store/routes.py @@ -1,8 +1,47 @@ from store import app, db -from flask import render_template, request, flash, redirect, url_for +from flask import render_template, request, flash, redirect, url_for, Flask, session from store.utility import * +# Official flask-login doc free liecense +#https://flask-login.readthedocs.io/en/latest/ +from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user +#Official flask-mail docum +#https://pythonhosted.org/Flask-Mail/ +from flask_mail import Mail, Message + + + + + +#Email configuration +app.config['MAIL_SERVER']='smtp.gmail.com' +app.config['MAIL_PORT'] = 465 +app.config['MAIL_USERNAME'] = 'antique.store7111@gmail.com' +app.config['MAIL_PASSWORD'] = 'nbouhrtaurcefgvj' +app.config['MAIL_USE_TLS'] = False +app.config['MAIL_USE_SSL'] = True + +mail = Mail(app) + + +#Create an instance of the LoginManager class and initialise +#it with your Flask application to begin using Flask-Login. +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view = 'login' + + + +@login_manager.user_loader +def load_user(user_id): + return User.query.get(int(user_id)) + +def get_user_id(): + if current_user.is_authenticated: + return current_user.user_id + else: + return None @app.route("/") @app.route("/index") @@ -10,6 +49,7 @@ def index(): return render_template('index.html', title='Home') # If the title is unspecified then a default is used that is set in base.html @app.route("/basket") +@login_required def basket(): return render_template('basket.html', title='Basket') @@ -21,26 +61,124 @@ def items(): def itemSets(): return render_template('itemSets.html', title='Item Sets') -@app.route("/login") -def login(): - return render_template('login.html', title='Login') + @app.route("/register", methods=['POST', 'GET']) def register(): - if request.method == "POST": + if request.method == "POST": + error = None # errors username = request.form['username'] password = request.form['password'] - #register_user(username,password ) - new_user = User(username=username, password= password) - db.session.add(new_user) - db.session.commit() - flash('Your account has been created! You are now able to log in', 'success') - return redirect(url_for('index')) - - return render_template('register.html', title='register') + email = request.form['email'] + phone_number = request.form['phone_number'] + user_exists = User.query.filter_by(username=username).first() + if user_exists: + error = 'The username you chose is already taken.' + return render_template('register.html', title='Register', error=error) + else: + new_user = User.create_user(username=username, password=password, email = email, phone_number = phone_number) + flash('Your account has been created! You are now able to log in.', 'success') + return redirect(url_for('index')) + return render_template('register.html', title='Register') +@app.route("/login", methods=['POST', 'GET']) +def login(): + if request.method == "POST": + username = request.form['username'] + password = request.form['password'] + user = User.query.filter_by(username=username).first() + if user and user.check_password(password): + login_user(user) + return redirect(url_for('index')) + else: + flash('Login unsuccessful. Please check username and password.', 'danger') + return render_template('login.html', title='Login') + + +@app.route('/logout') +@login_required +def logout(): + logout_user() + return redirect(url_for('index')) + + + +@app.route("/account") +@login_required +def account(): + return render_template('account.html', title='account') + +@app.route("/accountDetails") +@login_required +def accountDetails(): + a = get_user_id() + user = User.query.filter_by(user_id = a).first() + return render_template('accountDetails.html', user=user) + + + + +@app.route('/reset_password', methods=['GET', 'POST']) +def reset_password(): + if request.method == 'POST': + email = request.form['email'] + print("the email was :", email) + user = User.query.filter_by(email=email).first() + if user: + verification_code = getcode() + session['verification_code'] = verification_code + session['user_id'] = user.user_id + msg = Message('Password Reset Verification Code', sender='your-email@gmail.com', recipients=[email]) + msg.body = f"Hello, Your password reset verification code is {verification_code}" + mail.send(msg) + flash('A verification code has been sent to your email') + return redirect(url_for('verify_code')) + else: + flash('Invalid email address') + return render_template('reset_password.html') + +@app.route('/verify_code', methods=['GET', 'POST']) +def verify_code(): + if 'user_id' not in session or 'verification_code' not in session: + return redirect(url_for('reset_password')) + if request.method == 'POST': + verification_code = request.form['verification_code'] + if int(verification_code) == session['verification_code']: + session.pop('verification_code', None) + return redirect(url_for('reset_password_confirm')) + else: + flash('Invalid verification code') + return render_template('verify_code.html') + + + + +@app.route('/reset_password_confirm', methods=['GET', 'POST']) +def reset_password_confirm(): + if 'user_id' not in session: + return redirect(url_for('reset_password')) + user_id = session['user_id'] + if request.method == 'POST': + password = request.form['password'] + confirm_password = request.form['confirm_password'] + if password == confirm_password: + User.update_passwords(user_id, password) + session.pop('user_id', None) + flash('Your password has been successfully reset') + return redirect(url_for('login')) + else: + flash('Passwords do not match') + return render_template('reset_password_confirm.html') + + + +def getcode(): + randomchar = [str(random.choice(string.ascii_letters)) + str(random.randint(10,99)) for _ in range(4)] + randomdString = "" + for i in range(0, 4): + randomdString += randomchar[i % 4] - \ No newline at end of file + return randomdString \ No newline at end of file diff --git a/store/site.db b/store/site.db index d0ce28bb3cf992ceb0252d3a9fa53b791c2a52f3..fea106e361dc25f6ae2631941a7eee8a15d041cb 100644 Binary files a/store/site.db and b/store/site.db differ diff --git a/store/static/main.css b/store/static/main.css index 34c795dee901cd2bad61bb008e85fa7b0be71a2e..b024be0441c19fb12a87b4ab5cb17ce108b43d6d 100644 --- a/store/static/main.css +++ b/store/static/main.css @@ -1,6 +1,4 @@ -body { - background-color: white; -} + footer { background-color: black; @@ -17,7 +15,7 @@ div{ width:100%; } -li a { +.navbar li a { color:white; display: block; text-align: center; @@ -25,7 +23,7 @@ li a { padding: 14px 16px; } -ul { +.navbar ul { list-style-type: none; margin: 0; padding: 0; @@ -43,11 +41,11 @@ input[type=text] { font-size: 17px; } -li { +.navbar li { float:left; } -li a:hover { +.navbar li a:hover { background-color: white; color:black; } @@ -59,4 +57,14 @@ li a:hover { h1{ color: black; -} \ No newline at end of file + font-size: 30px; +} + +.error{ + text-decoration: none; + color:red; + font-size: 30px; +} + + + diff --git a/store/static/oldmain.css b/store/static/oldmain.css new file mode 100644 index 0000000000000000000000000000000000000000..5a6d56fa1e88f6da3a7191f198b2a4390bdab641 --- /dev/null +++ b/store/static/oldmain.css @@ -0,0 +1,69 @@ +body { + background-color: white; +} + +footer { + background-color: black; + color: white; + position: fixed; + bottom:0; + left:0; + width:100%; +} + +div{ + position:fixed; + left:0; + width:100%; +} + +.navbar li a { + color:white; + display: block; + text-align: center; + text-decoration: none; + padding: 14px 16px; +} + +.navbar ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: black; +} + + +input[type=text] { + float: left; + padding: 6px; + border: none; + margin-top: 8px; + margin-right: 16px; + font-size: 17px; +} + +.navbar li { + float:left; +} + +.navbar li a:hover { + background-color: white; + color:black; +} + +*{ + text-align: center; + font-family: sans-serif; +} + +h1{ + color: black; + font-size: 30px; +} + +.error{ + text-decoration: none; + color:red; + font-size: 30px; +} \ No newline at end of file diff --git a/store/templates/account.html b/store/templates/account.html new file mode 100644 index 0000000000000000000000000000000000000000..f34125563fa02951ee71a0d2a906387f381681b9 --- /dev/null +++ b/store/templates/account.html @@ -0,0 +1,9 @@ +{%extends 'base.html' %} + +{% block content %} + {% block title %} Account{% endblock %} + <h1>account Information</h1> + <ul> + <li><a href="{{ url_for('accountDetails', user_id=current_user.id)}}">View my Account details</a></li> + </ul> +{% endblock %} diff --git a/store/templates/accountDetails.html b/store/templates/accountDetails.html new file mode 100644 index 0000000000000000000000000000000000000000..38534505fa5a3b4e0f5e34d6e5338e480da3fee2 --- /dev/null +++ b/store/templates/accountDetails.html @@ -0,0 +1,10 @@ +{%extends 'base.html' %} + +{% block content %} + {% block title %} Account{% endblock %} + <h1>Account Details</h1> + <h1>Welcome, {{ user.username }}</h1> + <p>Your email address is: {{ user.email }}</p> + <p>Your Phone Number is: {{ user.phone_number }}</p> + +{% endblock %} diff --git a/store/templates/base.html b/store/templates/base.html index c5651ee62418048e9ac2fa40f0e846f144334d8d..fd45e13c47bc44501af23169dfe99348ce41e012 100644 --- a/store/templates/base.html +++ b/store/templates/base.html @@ -3,35 +3,58 @@ <head> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>{% block title %}{% endblock%}</title> - <link rel="stylesheet" type="text/css" href="../static/main.css"> <!-- Load the CSS. --> + <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}"> <!-- Load the CSS. --> </head> <body> - <header> - - <a class="logo" href="{{ url_for('index')}}">ANTIQUES ONLINE</a></li> + <header class="header"> + <a class="logo" href="{{ url_for('index')}}">ANTIQUES ONLINE</a> </header> - <div> - <ul> - <li><a href="{{ url_for('basket')}}">Basket</a></li> - <li><a href="{{ url_for('items')}}">Items</a></li> - <li><a href="{{ url_for('itemSets')}}">Item Sets</a></li> - <li><a href="{{ url_for('login')}}">Login</a></li>Register - <li><a href="{{ url_for('register')}}">Register</a></li> + <nav class="navbar"> + <ul class="navbar-list"> + <li><a href="{{ url_for('basket')}}">Basket</a></li> + <li><a href="{{ url_for('items')}}">Items</a></li> + <li><a href="{{ url_for('itemSets')}}">Item Sets</a></li> + + {% if current_user.is_authenticated %} + <li><a href="{{ url_for('account')}}">Account</a></li> + <li><a href="{{ url_for('logout')}}">Logout</a></li> + <li><a class="welcome">Welcome, {{ current_user.username }}!</a></li> + {% else %} + <li><a href="{{ url_for('login')}}">Login</a></li> + <li><a href="{{ url_for('register')}}">Register</a></li> + {% endif %} + + <li class="search"> + <form> + <button type="submit"><i class="fa fa-search"></i></button> + <input type="text" placeholder="Search..."> + </form> + </li> + </ul> + </nav> + + <div class="container"> + {% with messages = get_flashed_messages() %} + {% if messages %} + <ul class="flashes"> + {% for message in messages %} + <li class="error">{{ message }}</li> + {% endfor %} + </ul> + {% endif %} + {% endwith %} + + {% block content %} - <input type="text" placeholder="Search..."> - </ul> - </div> - - {% block content %} - - {% endblock %} <!-- Where HTML will go when extended. --> - - <footer> - <p>Contact Info: mail@mail.com + {% endblock %} <!-- Where HTML will go when extended. --> + </div> + + <footer class="footer"> + <p>Contact Info: mail@mail.com <br> Phone Number 0000 000 0000</p> - </footer> + </footer> </body> </html> diff --git a/store/templates/login.html b/store/templates/login.html index ecb8f698370cd1c9360e8bb6e7c3d4fcc6b555ff..b66a9320dd926b198f7ded2776735f446cb3c00c 100644 --- a/store/templates/login.html +++ b/store/templates/login.html @@ -1,5 +1,24 @@ -{%extends 'base.html' %} + +{%extends 'base.html' %} {% block content %} - {% block title %} Login | Antiques Online {% endblock %} + {% block title %} login | Antiques Online {% endblock %} + + +<form method="POST" action="{{ url_for('login') }}" style="float: center; text-align: center;"> + + <div class="container"> + <h1>Login</h1> + + <label for="username"><b>username</b></label> + <input type="username" placeholder="Enter username" name="username" id="username" required> + + <label for="password"><b>Password</b></label> + <input type="password" placeholder="Enter Password" name="password" id="password" required> + <a href="{{ url_for('reset_password')}}">Forgot my Password</a> + <input class="button" type="submit" value="Login" > + + </div> + + </form> {% endblock %} \ No newline at end of file diff --git a/store/templates/register.html b/store/templates/register.html index 3646bb8aa8602bbe58646a40066e27efd0fa65b1..77f4f4298e31b7ecc69610336cded3326b4bab3a 100644 --- a/store/templates/register.html +++ b/store/templates/register.html @@ -8,10 +8,21 @@ <form method="POST" action="{{ url_for('register') }}" style="float: center; text-align: center;"> <div class="container"> + <h1>Register</h1> + + {% if error %} + <p class=error><strong>Error:</strong> {{ error }} + {% endif %} <label for="username"><b>username</b></label> <input type="username" placeholder="Enter username" name="username" id="username" required> + + <label for="email"><b>E-mail</b></label> + <input type="email" placeholder="Enter E-mail" name="email" id="email" required> + + <label for="phone_number"><b>Phone Number</b></label> + <input type="phone_number" placeholder="Enter Number" name="phone_number" id="phone_number" required> <label for="password"><b>Password</b></label> <input type="password" placeholder="Enter Password" name="password" id="password" required> diff --git a/store/templates/reset_password.html b/store/templates/reset_password.html new file mode 100644 index 0000000000000000000000000000000000000000..74f003d0f5c6614adfb87e028cebb4fcdc7fac22 --- /dev/null +++ b/store/templates/reset_password.html @@ -0,0 +1,21 @@ + + +{%extends 'base.html' %} +{% block content %} + {% block title %} Reset code {% endblock %} + + +<form method="POST" action="{{ url_for('reset_password') }}" style="float: center; text-align: center;"> + + <div class="container"> + <h1>email verify</h1> + + <label for="email"><b>Enter your email</b></label> + <input type="email" placeholder="Enter your email" name="email" id="email" required> + + <input class="button" type="submit" value="Reset Password" > + + </div> + + </form> +{% endblock %} \ No newline at end of file diff --git a/store/templates/reset_password_confirm.html b/store/templates/reset_password_confirm.html new file mode 100644 index 0000000000000000000000000000000000000000..72ece7e626eef6394d74583945a70ffa0661b74d --- /dev/null +++ b/store/templates/reset_password_confirm.html @@ -0,0 +1,26 @@ + +{%extends 'base.html' %} +{% block content %} + {% block title %} Verify reset Password {% endblock %} + + + <form method="POST" action="{{ url_for('reset_password_confirm') }}" style="float: center; text-align: center;"> + + <div class="container"> + + <h1>Reset Password</h1> + + + <label for="password"><b>Password</b></label> + <input type="password" placeholder="Enter Password" name="password" id="password" required> + + + <label for="confirm_password"><b>confirm password</b></label> + <input type="confirm_password" placeholder="confirm password" name="confirm_password" id="confirm_password" required> + + <input class="button" type="submit" value="confirm password" > + + </div> + + </form> +{% endblock %} \ No newline at end of file diff --git a/store/templates/verify_code.html b/store/templates/verify_code.html new file mode 100644 index 0000000000000000000000000000000000000000..8022e304107099c7cc09f1ed38b5a652e99f9c61 --- /dev/null +++ b/store/templates/verify_code.html @@ -0,0 +1,21 @@ + + +{%extends 'base.html' %} +{% block content %} + {% block title %} Verify reset Password {% endblock %} + + +<form method="POST" action="{{ url_for('verify_code') }}" style="float: center; text-align: center;"> + + <div class="container"> + <h1>Login</h1> + + <label for="verification_code"><b>Enter your code </b></label> + <input type="verification_code" placeholder="Enter your code " name="verification_code" id="verification_code" required> + + <input class="button" type="submit" value="Reset Password" > + + </div> + + </form> +{% endblock %} \ No newline at end of file diff --git a/store/utility.py b/store/utility.py index 4d5b89538fd6ec226040f04a54f3c12f58a7205c..85c943cd43b4bb2160b5edfc01000017a9db3028 100644 --- a/store/utility.py +++ b/store/utility.py @@ -3,7 +3,8 @@ from store.models import * from sqlalchemy import select from store import * - +import random +import string # for autegenarating letters # Difference between filter and filter_by: https://stackoverflow.com/a/31560897 def get_unsold_items(): @@ -25,12 +26,4 @@ def get_itemsets_by_item_id(item_id): item_set_contained_id.append(item_set) return item_set_contained_id -# def get_itemsets_by_item_id(item_id): -# item_sets = select(ItemSet).where(ItemSet.items.any(Item.id==item_id)) -# flask_item_sets = db.session.execute(item_sets).all() -# flask_item_set = flask_item_sets[0][0] - - -# return 0 - - \ No newline at end of file +