+
+
 }})
+
tradeQuick
+
+
+ Enter your email and password to login to your account.
+
+
+
+
+
+
+
+

+
tradeQuick
+
+
+ Enter your email and password to login to your account.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Welcome back, User!
\ No newline at end of file
diff --git a/client-side/templates/register.html b/client-side/templates/register.html
new file mode 100644
index 0000000..1012782
--- /dev/null
+++ b/client-side/templates/register.html
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
Register
+
+
+
+
+
+
+
+
+ {% with messages = get_flashed_messages() %}
+ {% if messages %}
+
+ {% for message in messages %}
+
+ {{ message[1] }}
+
+ {% endfor %}
+
+ {% endif %}
+ {% endwith %}
+
+
+
 }})
+
+
+
+ Start your journey with us.
+
+
+
+
+
+
Already have an account? Login
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server-side/.gitignore b/server-side/.gitignore
new file mode 100644
index 0000000..be75cbc
--- /dev/null
+++ b/server-side/.gitignore
@@ -0,0 +1,3 @@
+.vscode/
+.env
+__pycache__/
\ No newline at end of file
diff --git a/server-side/API_DOCUMENTATION.md b/server-side/API_DOCUMENTATION.md
new file mode 100644
index 0000000..9fa0a5d
--- /dev/null
+++ b/server-side/API_DOCUMENTATION.md
@@ -0,0 +1,431 @@
+## TradeQuick
+
+**This web app is a market place for selling used items that someone else might be interested in, the seller uploads certain information such as the photos of it, description and price and the buyer can either comment on the post or send private message. This is a document that is describing or explaining every API endpoint(TradeQuick) and it's usage.**
+
+### Users
+
+#### GET http://0.0.0.0:5000/api/v1/users
+
+##### Description
+
+* This endpoint does not require authentication.
+* This endpoint retrieves all registered users.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/users"
+```
+
+#### POST http://0.0.0.0:5000/api/v1/users
+
+##### Description
+
+* This endpoint does not require authentication.
+* Creates a new user with the data below
+* This will also automtically create a default avatar for you as your profile pics
+which you can update later to the photo you would like to use.
+
+| key | value |
+|:----------------:|:-------------:|
+| fullname | John Doe |
+| email |johnd@gmail.com|
+| password | 1234 |
+| gender | male |
+| about | wefsdwewed |
+| phone1 | 232-442-646 |
+| phone2 | 731-843-742 |
+| address | 23D3 cray ave|
+| town | old town |
+| city | new city |
+| state | CA |
+
+Example usage could be like this;
+```
+curl -XPOST 'http://0.0.0.0:5000/api/v1/users' \
+--form 'fullname="Glory Maurice"' \
+--form 'email="glo.maurice@gmail.com"' \
+--form 'gender="Female"' \
+--form 'password="1234"' \
+--form 'about="I love Jesus"' \
+--form 'phone1="232-323-2322"' \
+--form 'address="23d32 bradway ave"' \
+--form 'photo="new_pic.png"' \
+--form 'town="maryland"' \
+--form 'city="boston"' \
+--form 'state="NY"'
+```
+* This values are mock data that are used for this example purpose only.
+
+#### POST http://0.0.0.0:5000/api/v1/users/login
+
+##### Description
+
+* This endpoint creates a new login session for a user with their email and password
+* With this session created users are authenticated automatically.
+
+Example usage could be like this;
+```
+curl -XPOST 'http://0.0.0.0:5000/api/v1/users/login' \
+--form 'email="enola.fish@gmail.com"' \
+--form 'password="1234"'
+```
+
+
+#### GET http://0.0.0.0:5000/api/v1/users/
+
+##### Description
+
+* This endpoint requires authentication which will be the current user to carry out this operation.
+* This endpoint retrieves a registered user based on their user_id.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/users/"
+```
+
+#### GET http://0.0.0.0:5000/api/v1/users/me
+
+##### Description
+
+* This endpoint requires authentication which will be the current user to carry out this operation.
+* This endpoint gets the current authenticated user.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/users/me"
+```
+
+#### POST http://0.0.0.0:5000/api/v1/users/logout
+
+##### Description
+
+* This endpoint logs out a user and deletes the authenticated session
+
+Example usage could be like this;
+```
+curl -XPOST "http://0.0.0.0:5000/api/v1/users/logout"
+```
+
+
+#### POST http://0.0.0.0:5000/api/v1/users/
+
+##### Description
+
+* This endpoint requires authentication which will be the current user to carry out this operation.
+* This endpoint reset a user password based on their user_id.
+* The field below are required to reset their password
+
+| key | value |
+|:------------:|:-------------:|
+| email |johnd@gmail.com|
+| password | 1234 |
+| new_password | pass1234 |
+
+Example usage could be like this;
+```
+curl -XPOST "http://0.0.0.0:5000/api/v1/users/"
+--form 'email="johnd@gmail.com"' \
+--form 'password="1234"' \
+--form 'new_password="new_pass1234"'
+```
+
+
+#### DELETE http://0.0.0.0:5000/api/v1/users/
+
+##### Description
+
+* This endpoint requires authentication which will be the current user to carry out this operation.
+* This endpoint deletes the current user entire account and it's relations.
+
+Example usage could be like this;
+```
+curl -XDELETE "http://0.0.0.0:5000/api/v1/users/"
+```
+
+#### PUT http://0.0.0.0:5000/api/v1/users/
+
+##### Description
+
+* This endpoint requires authentication which will be the current user to carry out this operation.
+* This endpoint updates the information of the user.
+* This endpoint updates any information the user might want to change
+
+Example usage could be like this;
+```
+curl -XPUT "http://0.0.0.0:5000/api/v1/users/19ebfab1-db5d-499c-95a5-fb74cbfd5d44" -H "Content-type: multipart/form-data" \
+-F "fullname=Kayla Fisher" \
+-F "phone1=341-545-2342" \
+-F "about=God is working it out" \
+-F"address=34D newton ave" \
+-F "town=New Haven" \
+-F "city=Los Angeles" \
+-F "state=CA" \
+-F "photo=@../server-side/api/v1/views/landscape.jpg"
+```
+
+
+### Items
+
+#### GET http://0.0.0.0:5000/api/v1/items
+
+##### Description
+
+* This endpoint does not requires authentication.
+* This endpoint retrieves all item posted by all registered users.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/items"
+```
+
+#### POST http://0.0.0.0:5000/api/v1/items
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint post an item for sale by a registered user.
+
+| key | value |
+|:----------------:|:-------------:|
+| item_name | Watch |
+| description |Quality and durable watch|
+| price | 1000000 |
+| photo1 | 3v-a42-6e4.jpg|
+| photo2 | 32-df-23ds.jpg|
+| photo3 | 3df-2ecwsd.jpg|
+
+Example usage could be like this;
+```
+curl -XPOST "http://0.0.0.0:5000/api/v1/items"
+-H "Content-type: multipart/form-data" \
+-F "item_name=Watch" \
+-F "description=Quality and durable watch" \
+-F "price=100000" \
+-F "photo1=@../path/to/gadget1.jpg" \
+-F "photo2=@../path/to/gadget2.jpg"
+```
+
+#### GET http://0.0.0.0:5000/api/v1/items/
+
+##### Description
+
+* This endpoint does not require authentication.
+* This endpoint retrieves all items posted by a user based on their user_id.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/items/"
+```
+
+#### GET http://0.0.0.0:5000/api/v1/item/
+
+##### Description
+
+* This endpoint does not require authentication.
+* This endpoint retrieves an item posted by a user based on it's item_id.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/item/"
+```
+
+#### PUT http://0.0.0.0:5000/api/v1/items/
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint updates an item based on it's item_id.
+
+Example usage could be like this;
+```
+curl -XPUT "http://0.0.0.0:5000/api/v1/items/"
+-H "Content-type: multipart/form-data" \
+-F "item_name=TV" \
+-F "description=LG 42 inch HDMI Tv" \
+-F "price=150000" \
+-F "photo1=@../path/to/TV1.jpg" \
+-F "photo2=@../path/to/TV2.jpg"
+```
+
+#### DELETE http://0.0.0.0:5000/api/v1/items/
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint deletes an item posted by a user based on it's item_id.
+
+Example usage could be like this;
+```
+curl -XDELETE "http://0.0.0.0:5000/api/v1/item/"
+```
+
+### Ratings
+
+#### POST http://0.0.0.0:5000/api/v1/ratings
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint post a new rating.
+
+Example usage could be like this;
+```
+curl -XPOST "http://0.0.0.0:5000/api/v1/ratings" \
+-H "Content-type: multipart/form-data" \
+-F "user_id=19ebfab1-db5d-499c-95a5-fb74cbfd5d44" \
+-F "comment=I love this watch" \
+-F "rating=4"
+```
+
+#### GET http://0.0.0.0:5000/api/v1/ratings
+
+##### Description
+
+* This endpoint does not require authentication.
+* This endpoint retrieves all ratings posted by all registered users.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/ratings"
+```
+
+### Likes
+
+#### POST http://0.0.0.0:5000/api/v1/likes/0
+
+##### Description
+
+* This endpoint requires authentication
+* Passing the parameter 0 or 1 set dislike or like an item respectively
+* Sending a post request twice with the same parameter undo the dislike or like and sets it to like or dislike respectively.
+
+Example usage could be like this;
+```
+curl -XPOST "http://0.0.0.0:5000/api/v1/likes/0" -d "user_id=&item_id=" --cookie=""
+```
+
+#### GET http://0.0.0.0:5000/api/v1/likes/
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint retrieves all likes and dislikes based on an item_id.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/likes/" --cookie=""
+```
+
+### Comments
+
+#### POST http://0.0.0.0:5000/api/v1/comments
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint post a new comment.
+
+Example usage could be like this;
+```
+curl -XPOST "http://0.0.0.0:5000/api/v1/comments" \
+-H "Content-type: multipart/form-data" \
+-F "commenter=19ebfab1-db5d-499c-95a5-fb74cbfd5d44" \
+-F "comment=I love this TV" \
+-F "item_id=a2585ea3-dd0b-451a-83a6-a1ee52c02580"
+```
+
+#### PATCH http://0.0.0.0:5000/api/v1/comments/
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint updates a comment based on their comment_id.
+
+Example usage could be like this;
+```
+curl -XPATCH "http://0.0.0.0:5000/api/v1/comments/" -d "comment=Do you have the latest one?" --cookie=
+```
+
+#### http://0.0.0.0:5000/api/v1/comments/
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint retreives a comment based on the item_id.
+
+Example usage could be like this;
+```
+curl "http://0.0.0.0:5000/api/v1/comments/ --cookie=
+```
+
+#### DELETE http://0.0.0.0:5000/api/v1/comments/comment_id
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint deletes a comment based on the comment_id.
+
+Example usage could be like this;
+```
+curl -XDELETE "http://0.0.0.0:5000/api/v1/comments/ --cookie=
+```
+
+### Chats
+
+#### POST http://0.0.0.0:5000/api/v1/messages
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint post a new message.
+
+Example usage could be like this;
+```
+curl -XPOST "http://0.0.0.0:5000/api/v1/messages \
+-H "Content-type: multipart/form-data" \
+-F "sender_id=" \
+-F "receiver_id=" \
+-F "message=I love this TV" \
+--cookie=
+```
+
+#### PATCH http://0.0.0.0:5000/api/v1/messages/message_id
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint updates an already sent message based on the message_id.
+
+Example usage could be like this;
+```
+curl -XPATCH "http://0.0.0.0:5000/api/v1/messages \
+-H "Content-type: multipart/form-data" \
+-F "message=Do you have more of this TV?" \
+--cookie=
+```
+
+#### DELETE http://0.0.0.0:5000/api/v1/messages/message_id
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint deletes a message based on the message_id.
+
+Example usage could be like this;
+```
+curl -XDELETE "http://0.0.0.0:5000/api/v1/messages/message_id --cookie=
+```
+
+#### DELETE http://0.0.0.0:5000/api/v1/messages/user_id
+
+##### Description
+
+* This endpoint requires authentication.
+* This endpoint deletes all message posted by a user.
+
+Example usage could be like this;
+```
+curl -XDELETE "http://0.0.0.0:5000/api/v1/messages/user_id --cookie=
+```
+
+###### © 2023 tradeQuick. All rights reserved.
\ No newline at end of file
diff --git a/server-side/Project.code-workspace b/server-side/Project.code-workspace
new file mode 100644
index 0000000..407c760
--- /dev/null
+++ b/server-side/Project.code-workspace
@@ -0,0 +1,8 @@
+{
+ "folders": [
+ {
+ "path": "../.."
+ }
+ ],
+ "settings": {}
+}
\ No newline at end of file
diff --git a/server-side/README.md b/server-side/README.md
new file mode 100644
index 0000000..55e5757
--- /dev/null
+++ b/server-side/README.md
@@ -0,0 +1 @@
+**Read through the API_DOCUMENTATION.md file to understand the usage of TradeQuick's API**
diff --git a/client-side/about.html b/server-side/api/__init__.py
similarity index 100%
rename from client-side/about.html
rename to server-side/api/__init__.py
diff --git a/client-side/portfolio.html b/server-side/api/v1/__init__.py
similarity index 100%
rename from client-side/portfolio.html
rename to server-side/api/v1/__init__.py
diff --git a/server-side/api/v1/app.py b/server-side/api/v1/app.py
new file mode 100644
index 0000000..7f41714
--- /dev/null
+++ b/server-side/api/v1/app.py
@@ -0,0 +1,117 @@
+"""
+App Initialiser
+"""
+
+from flask import Flask, jsonify, abort, request, flash, redirect, url_for, render_template
+from flask_cors import (CORS, cross_origin)
+from api.v1.views import app_views
+from api.v1.views import auth
+from dotenv import load_dotenv
+from os import getenv
+import os
+from models.db import DBStorage
+
+
+db = DBStorage()
+
+
+load_dotenv()
+
+
+app = Flask(
+ __name__, template_folder='/mnt/c/Users/udohd/Project/tradeQuick/client-side/templates', static_folder='/mnt/c/Users/udohd/Project/tradeQuick/client-side/static', static_url_path='/static')
+app.register_blueprint(app_views)
+app.register_blueprint(auth)
+app.config['SECRET_KEY'] = getenv("API_SECRET_KEY")
+CORS(app, resources={r"/api/v1/*": {"origins": "*"}})
+auth = None
+AUTH_TYPE = getenv("AUTH_TYPE")
+if AUTH_TYPE == "auth":
+ from api.v1.auth.auth import Auth
+ auth = Auth()
+if AUTH_TYPE == "basic_auth":
+ from api.v1.auth.basic_auth import BasicAuth
+ auth = BasicAuth()
+if AUTH_TYPE == "session_auth":
+ from api.v1.auth.session_auth import SessionAuth
+ auth = SessionAuth()
+if AUTH_TYPE == "session_exp_auth":
+ from api.v1.auth.session_exp_auth import SessionExpAuth
+ auth = SessionExpAuth()
+if AUTH_TYPE == "session_db_auth":
+ from api.v1.auth.session_db_auth import SessionDBAuth
+ auth = SessionDBAuth()
+
+
+@app.teardown_appcontext
+def close_db(error):
+ """
+ close database session
+ """
+ with db:
+ db.close()
+
+
+@app.errorhandler(404)
+def not_found(error) -> str:
+ """ Not found handler
+ """
+ return jsonify({"error": "Not found"}), 404
+
+
+@app.errorhandler(401)
+def unauthorized(error) -> str:
+ """ Unauthorized handler
+ """
+ return jsonify({"error": "Unauthorized"}), 401
+
+
+@app.errorhandler(403)
+def forbidden(error) -> str:
+ """ Forbidden handler
+ """
+ return jsonify({"error": "Forbidden"}), 403
+
+
+@app.before_request
+def before_request_func():
+ """ Before request handler
+ """
+
+ excluded_paths = ['/api/v1/status/',
+ '/api/v1/unauthorized/',
+ '/api/v1/forbidden/',
+ '/api/v1/users/login/',
+ '/api/v1/users/',
+ '/api/v1/items/',
+ '/api/v1/signup/',
+ '/api/v1/login/',
+ '/auth/signup/',
+ '/auth/login/',
+ '/static/images/logo.png/',
+ '/static/images/logo1.png/',
+ '/static/scripts/register.js/',
+ '/static/scripts/login.js/',
+ '/favicon.ico/',
+ '/logo.png',
+ '/auth/user/login/',
+ '/static/images/banner.jpg/',
+ ]
+ if auth is None:
+ pass
+ else:
+ setattr(request, 'current_user', auth.current_user(request))
+ if auth.require_auth(request.path, excluded_paths):
+ pass
+ if auth.authorization_header(request) is None and auth.session_cookie(request) is None:
+ flash('Session expired. Please login again', 'danger')
+ return redirect(url_for('auth.login_page'))
+ if auth.current_user(request) is None:
+ flash('Session expired. Please login again', 'danger')
+ return redirect(url_for('auth.login_page'))
+
+
+if __name__ == "__main__":
+ host = getenv("API_HOST", "0.0.0.0")
+ port = getenv("API_PORT", "5000")
+ app.run(host=host, port=port, debug=True)
diff --git a/server-side/api/v1/auth/__init__.py b/server-side/api/v1/auth/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/server-side/api/v1/auth/auth.py b/server-side/api/v1/auth/auth.py
new file mode 100644
index 0000000..dedc641
--- /dev/null
+++ b/server-side/api/v1/auth/auth.py
@@ -0,0 +1,53 @@
+"""
+Auth class
+"""
+
+from typing import List, TypeVar
+
+
+class Auth:
+ """
+ Auth class
+ """
+
+ def require_auth(self, path: str, excluded_paths: List[str]) -> bool:
+ """
+ require_auth
+ """
+ if path is None:
+ return True
+ if excluded_paths is None or excluded_paths == []:
+ return True
+ if path[-1] != '/':
+ path += '/'
+ if path in excluded_paths:
+ return False
+ else:
+ return True
+
+ def authorization_header(self, request=None) -> str:
+ """
+ authorization_header
+ """
+ if request is None:
+ return None
+ value = request.headers.get('Authorization')
+ if value is None or value == '':
+ return None
+ else:
+ return value
+
+ def current_user(self, request=None) -> TypeVar('User'):
+ """
+ current_user
+ """
+ return None
+
+ def session_cookie(self, request=None):
+ """
+ - Returns a cookie value from a requests
+ """
+ if request is None:
+ return None
+ cookie_value = request.cookies.get('_my_session_id')
+ return cookie_value
diff --git a/server-side/api/v1/auth/basic_auth.py b/server-side/api/v1/auth/basic_auth.py
new file mode 100644
index 0000000..e5adf06
--- /dev/null
+++ b/server-side/api/v1/auth/basic_auth.py
@@ -0,0 +1,85 @@
+"""
+This file contains the basic authentication for the API
+"""
+
+import base64
+from flask import abort, jsonify, request, abort
+from api.v1.auth.auth import Auth
+from typing import TypeVar
+
+
+class BasicAuth(Auth):
+ """
+ BasicAuth class
+ """
+
+ def extract_base64_authorization_header(self, authorization_header: str) -> str:
+ """
+ extract_base64_authorization_header
+ """
+ if authorization_header is None:
+ return None
+ if type(authorization_header) is not str:
+ return None
+ split_header = authorization_header.split(' ')
+ if split_header[0] != 'Basic':
+ return None
+ if len(split_header) < 2:
+ return None
+ return split_header[1]
+
+ def decode_base64_authorization_header(self, base64_authorization_header: str) -> str:
+ """
+ decode_base64_authorization_header
+ """
+ if base64_authorization_header is None:
+ return None
+ if type(base64_authorization_header) is not str:
+ return None
+ try:
+ return base64.b64decode(base64_authorization_header).decode('utf-8')
+ except Exception:
+ return None
+
+ def extract_user_credentials(self, decoded_base64_authorization_header: str) -> (str, str):
+ """
+ extract_user_credentials
+ """
+ if decoded_base64_authorization_header is None:
+ return None, None
+ if type(decoded_base64_authorization_header) is not str:
+ return None, None
+ if ':' not in decoded_base64_authorization_header:
+ return None, None
+ split_header = decoded_base64_authorization_header.split(':')
+ return split_header[0], split_header[1]
+
+ def user_object_from_credentials(self, user_email: str, user_pwd: str) -> TypeVar('User'):
+ """
+ user_object_from_credentials
+ """
+ if user_email is None or user_pwd is None:
+ return None
+ if type(user_email) is not str or type(user_pwd) is not str:
+ return None
+
+ from models.db import DBStorage
+ DB = DBStorage()
+ with DB:
+ user = DB.find_user_by_email(user_email)
+ if user is None:
+ return None
+ if user.password == user_pwd:
+ return jsonify(user.to_dict()), 200
+
+ def current_user(self, request=None) -> TypeVar('User'):
+ """
+ Load the user from the request
+ """
+ auth_header = self.authorization_header(request)
+ extract_base64 = self.extract_base64_authorization_header(auth_header)
+ decode_base64 = self.decode_base64_authorization_header(extract_base64)
+ user_credentials = self.extract_user_credentials(decode_base64)
+ user_object = self.user_object_from_credentials(
+ user_credentials[0], user_credentials[1])
+ return user_object
diff --git a/server-side/api/v1/auth/session_auth.py b/server-side/api/v1/auth/session_auth.py
new file mode 100644
index 0000000..9321e17
--- /dev/null
+++ b/server-side/api/v1/auth/session_auth.py
@@ -0,0 +1,58 @@
+from api.v1.auth.auth import Auth
+from uuid import uuid4
+from typing import TypeVar
+
+
+class SessionAuth(Auth):
+ """
+ - Session Auth Implementation Class
+ """
+ user_id_by_session_id = {}
+
+ def create_session(self, user_id: str = None) -> str:
+ """
+ A method that creates a Session ID for a user_id
+ """
+ if user_id and isinstance(user_id, str):
+ session_id = str(uuid4())
+ self.user_id_by_session_id[session_id] = user_id
+ return session_id
+ return None
+
+ def user_id_for_session_id(self, session_id: str = None) -> str:
+ """
+ A method that returns a User ID based on a Session ID
+ """
+ if session_id and isinstance(session_id, str):
+ return self.user_id_by_session_id.get(session_id)
+ return None
+
+ def current_user(self, request=None) -> TypeVar('User'):
+ """
+ - Overloads Auth and retrieves User instance for a request
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ session_cookie = self.session_cookie(request)
+ user_id = self.user_id_for_session_id(session_cookie)
+ with db:
+ user = db.find_user_by_id(user_id)
+ return user
+ except Exception:
+ return None
+
+ def destroy_session(self, request=None):
+ """
+ - Deletes the user session / logout
+ """
+ if request is None:
+ return False
+ sess_cookie = self.session_cookie(request)
+ if sess_cookie is None:
+ return False
+ user_id = self.user_id_for_session_id(sess_cookie)
+ if user_id is None:
+ return False
+ del self.user_id_by_session_id[sess_cookie]
+ return True
diff --git a/server-side/api/v1/auth/session_db_auth.py b/server-side/api/v1/auth/session_db_auth.py
new file mode 100644
index 0000000..307df73
--- /dev/null
+++ b/server-side/api/v1/auth/session_db_auth.py
@@ -0,0 +1,102 @@
+from api.v1.auth.session_exp_auth import SessionExpAuth
+from datetime import datetime, timedelta
+import os
+from dotenv import load_dotenv
+
+load_dotenv()
+
+
+class SessionDBAuth(SessionExpAuth):
+ """
+ - SessionDBAuth class
+ """
+
+ def __init__(self) -> None:
+ try:
+ session_duration = int(os.getenv('SESSION_DURATION'))
+ except Exception:
+ session_duration = 0
+ self.session_duration = session_duration
+
+ def create_session(self, user_id=None):
+ session_id = super().create_session(user_id)
+ if session_id is None or not session_id:
+ return None
+ try:
+ from models.db import DBStorage
+ db = DBStorage()
+ session_data = {
+ "user_id": user_id,
+ "session_id": session_id
+ }
+ with db:
+ session = db.create_session(session_data)
+ if session is None:
+ return None
+ db.save()
+ db.close()
+ return session.session_id
+ except Exception as e:
+ print(e)
+ return None
+
+ def user_id_for_session_id(self, session_id=None):
+ """
+ - User ID for session ID
+ """
+ try:
+ if session_id is None:
+ return None
+
+ from models.db import DBStorage
+ db = DBStorage()
+ with db:
+ session = db.find_session_by_id(session_id)
+ if session is None:
+ return None
+
+ if self.session_duration > 0:
+ created_at = session.created_at
+ if not created_at:
+ return None
+
+ expiration_time = created_at + \
+ timedelta(minutes=self.session_duration)
+
+ if expiration_time < datetime.now():
+ try:
+ db.delete_session(session)
+ except Exception as e:
+ print(f"Error during session deletion: {e}")
+ return None
+ return None # Session expired, return None
+ db.save()
+ db.close()
+
+ return session.user_id
+
+ except Exception as e:
+ print(f"Error during user_id_for_session_id: {e}")
+ return None
+
+ def destroy_session(self, request=None):
+ """
+ - Destroy session
+ """
+ try:
+ session_id = self.session_cookie(request)
+ if session_id is None:
+ return False
+ from models.db import DBStorage
+ db = DBStorage()
+ with db:
+ session = db.find_session_by_id(session_id)
+ if session is None:
+ return False
+ db.delete_session(session)
+ db.save()
+ db.close()
+ return True
+ except Exception as e:
+ print(e)
+ return False
diff --git a/server-side/api/v1/auth/session_exp_auth.py b/server-side/api/v1/auth/session_exp_auth.py
new file mode 100644
index 0000000..6cd92f6
--- /dev/null
+++ b/server-side/api/v1/auth/session_exp_auth.py
@@ -0,0 +1,61 @@
+"""
+A module for session expiration
+"""
+from api.v1.auth.session_auth import SessionAuth
+import os
+from datetime import datetime, timedelta
+
+
+class SessionExpAuth(SessionAuth):
+ """
+ A session expiration class
+ """
+
+ def __init__(self) -> None:
+ try:
+ session_duration = int(os.getenv('SESSION_DURATION'))
+ except Exception:
+ session_duration = 0
+ self.session_duration = session_duration
+
+ def create_session(self, user_id=None):
+ session_id = super().create_session(user_id)
+ if session_id is None or not session_id:
+ return None
+ session_dict = {
+ "user_id": user_id,
+ "created_at": datetime.now()
+ }
+
+ self.user_id_by_session_id[session_id] = session_dict
+ return session_id
+
+ def user_id_for_session_id(self, session_id=None):
+ """
+ - User ID for session ID
+ """
+ try:
+ if session_id is None:
+ return None
+ sess_info = self.user_id_by_session_id.get(session_id)
+ if not sess_info:
+ return None
+
+ if self.session_duration <= 0:
+ user_id = sess_info.get("user_id")
+ return user_id
+
+ created_at = sess_info.get("created_at")
+ if not created_at:
+ return None
+
+ expiration_time = created_at + \
+ timedelta(seconds=self.session_duration)
+ if expiration_time < datetime.now():
+ return None
+
+ user_id = sess_info.get("user_id")
+ return user_id
+ except Exception as e:
+ print(e)
+ return None
diff --git a/server-side/api/v1/views/__init__.py b/server-side/api/v1/views/__init__.py
new file mode 100644
index 0000000..7f8a63e
--- /dev/null
+++ b/server-side/api/v1/views/__init__.py
@@ -0,0 +1,14 @@
+""" DocDocDocDocDocDoc
+"""
+from flask import Blueprint
+
+app_views = Blueprint("app_views", __name__, url_prefix="/api/v1")
+auth = Blueprint("auth", __name__, url_prefix="/auth")
+
+from api.v1.views.items import *
+from api.v1.views.chats import *
+from api.v1.views.likes import *
+from api.v1.views.comments import *
+from api.v1.views.ratings import *
+from api.v1.views.users import *
+from api.v1.views.index import *
diff --git a/server-side/api/v1/views/chats.py b/server-side/api/v1/views/chats.py
new file mode 100644
index 0000000..585faff
--- /dev/null
+++ b/server-side/api/v1/views/chats.py
@@ -0,0 +1,124 @@
+from flask import abort, request, jsonify
+from os import getenv
+from api.v1.views import app_views
+
+
+
+@app_views.route('/messages', methods=['POST'], strict_slashes=False)
+def create_message():
+ """
+ - Create a new message
+ """
+ message_data = {}
+ sender_id = request.form.get('sender_id')
+ receiver_id = request.form.get('receiver_id')
+ message = request.form.get('message')
+ if not sender_id:
+ abort(400, "Missing sender_id")
+ if not receiver_id:
+ abort(400, "Missing receiver_id")
+ if not message:
+ abort(400, "Missing message")
+ message_data['sender_id'] = sender_id
+ message_data['receiver_id'] = receiver_id
+ message_data['message'] = message
+
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ auth_user = request.current_user
+ receiver = db.find_user_by_id(message['receiver_id'])
+ if auth_user is not None:
+ if auth_user.user_id != message_data['sender_id']:
+ abort(401, "Unauthorized")
+ if receiver is None:
+ abort(404, "No user found for that ID")
+ new_message = db.create_message(message_data)
+ if not new_message:
+ abort(404, "Error creating message")
+ db.save()
+ return jsonify(new_message.to_dict()), 201
+ except Exception as e:
+ abort(400)
+
+
+@app_views.route('/messages/', methods=['PATCH'], strict_slashes=False)
+def update_message(message_id):
+ from models.db import DBStorage
+ db = DBStorage()
+ message_data = {}
+ message = request.form.get('message')
+ if not message:
+ abort(400, "Missing message")
+ message_data['message'] = message
+ try:
+ with db:
+ auth_user = request.current_user
+ new_message = db.get_messages_by_message_id(message_id)
+ if not new_message:
+ abort(404, "message not found")
+ if auth_user is not None:
+ if auth_user.user_id != new_message.sender_id:
+ abort(401, "Unauthorized")
+ new_message.message = message_data['message']
+ db.save()
+ return jsonify(new_message.to_dict()), 200
+ except Exception as e:
+ print(e)
+ abort(401)
+
+
+@app_views.route('/message/', methods=['DELETE'], strict_slashes=False)
+def delete_message(message_id):
+ """
+ - Delete a message
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ auth_user = request.current_user
+ message = db.get_messages_by_message_id(message_id)
+ if not message:
+ abort(404, "message not found")
+ if auth_user is not None:
+ if auth_user.user_id != message.sender_id:
+ abort(401, "Unauthorized")
+ db.delete(message)
+ db.save()
+ return jsonify({}), 200
+ except Exception as e:
+ abort(401)
+
+
+@app_views.route('/messages/', methods=['DELETE'], strict_slashes=False)
+def delete_all_user_chat(user_id):
+ """
+ - Delete all messages from a user
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ auth_user = request.current_user
+
+ # Retrieve messages
+ messages = db.get_messages_by_user_id(user_id)
+
+ # Check if there are no messages
+ if not messages:
+ abort(404, "Messages not found")
+
+ # Check sender authorization and delete messages
+ for message in messages:
+ if auth_user is not None and auth_user.user_id != message.sender_id:
+ abort(401, "Unauthorized")
+ db.delete(message)
+
+ # Save changes after all messages have been deleted
+ db.save()
+ return jsonify({}), 200
+ except Exception as e:
+ print(e)
+ return jsonify({}), 200
diff --git a/server-side/api/v1/views/comments.py b/server-side/api/v1/views/comments.py
new file mode 100644
index 0000000..c6c0005
--- /dev/null
+++ b/server-side/api/v1/views/comments.py
@@ -0,0 +1,115 @@
+from flask import abort, request, jsonify
+from dotenv import load_dotenv
+from os import getenv
+from api.v1.views import app_views
+
+load_dotenv()
+
+
+@app_views.route('/comments', methods=['POST'], strict_slashes=False)
+def comments():
+ """Create a new comment"""
+ from models.db import DBStorage
+ db = DBStorage()
+ comment_data = {}
+
+ commenter = request.form.get('commenter')
+ item_id = request.form.get('item_id')
+ comment = request.form.get('comment')
+ if not commenter:
+ abort(400, "Missing commenter")
+ if not item_id:
+ abort(400, "Missing item_id")
+ if not comment:
+ abort(400, "Missing comment")
+
+ comment_data['commenter'] = commenter
+ comment_data['item_id'] = item_id
+ comment_data['comment'] = comment
+
+ try:
+ with db:
+ auth_user = request.current_user
+ if auth_user is not None:
+ if auth_user.user_id != comment_data['commenter']:
+ abort(401, "Unauthorized")
+ new_comment = db.create_comment(comment_data)
+ if not new_comment:
+ abort(400, "Error creating comment")
+ db.save()
+ return jsonify(new_comment.to_dict()), 201
+ except Exception as e:
+ abort(401)
+
+
+@app_views.route('/comments/', methods=['PATCH'], strict_slashes=False)
+def update_comment(comment_id):
+ """Update a comment"""
+ from models.db import DBStorage
+ db = DBStorage()
+ comment_data = {}
+
+ comment = request.form.get('comment')
+ if not comment:
+ abort(400, "Missing comment")
+
+ comment_data['comment'] = comment
+
+ try:
+ with db:
+ auth_user = request.current_user
+ comment = db.get_comments_by_comment_id(comment_id)
+ if not comment:
+ abort(404, "Comment not found")
+ if auth_user is not None:
+ if auth_user.user_id != comment.commenter:
+ abort(401, "Unauthorized")
+ comment.comment = comment_data['comment']
+ db.save()
+ return jsonify(comment.to_dict()), 200
+ except Exception as e:
+ print(e)
+ abort(401)
+
+
+@app_views.route('/comments/', methods=['GET'], strict_slashes=False)
+def get_comments(item_id):
+ """Get comments for an item"""
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ comments = db.get_comments_by_item_id(item_id)
+ if not comments:
+ abort(404, "No comments found")
+ all_comments = [comment.to_dict() for comment in comments]
+ count_comments = len(all_comments)
+ response = {
+ 'count': count_comments,
+ 'comments': all_comments
+ }
+ return jsonify(response), 200
+ except Exception as e:
+ abort(400)
+
+
+@app_views.route('/comments/', methods=['DELETE'], strict_slashes=False)
+def delete_comment(comment_id):
+ """Delete a comment"""
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ auth_user = request.current_user
+ comment = db.get_comments_by_comment_id(comment_id)
+ if not comment:
+ abort(404, "Comment not found")
+ if auth_user is not None:
+ if auth_user.user_id != comment.commenter:
+ abort(401, "Unauthorized")
+ db.delete(comment)
+ db.save()
+ return jsonify({}), 200
+ except Exception as e:
+ print(e)
+ abort(401)
diff --git a/server-side/api/v1/views/gadget1.jpg b/server-side/api/v1/views/gadget1.jpg
new file mode 100644
index 0000000..7075caf
Binary files /dev/null and b/server-side/api/v1/views/gadget1.jpg differ
diff --git a/server-side/api/v1/views/gadget2.jpg b/server-side/api/v1/views/gadget2.jpg
new file mode 100644
index 0000000..a6cd957
Binary files /dev/null and b/server-side/api/v1/views/gadget2.jpg differ
diff --git a/server-side/api/v1/views/gadget3.jpg b/server-side/api/v1/views/gadget3.jpg
new file mode 100644
index 0000000..1c6d301
Binary files /dev/null and b/server-side/api/v1/views/gadget3.jpg differ
diff --git a/server-side/api/v1/views/index.py b/server-side/api/v1/views/index.py
new file mode 100644
index 0000000..fe2cc0d
--- /dev/null
+++ b/server-side/api/v1/views/index.py
@@ -0,0 +1,32 @@
+"""
+Modules for index endpoints
+"""
+from api.v1.views import app_views
+from flask import abort, jsonify, request, abort
+
+
+@app_views.route('/status', methods=['GET'], strict_slashes=False)
+def get_status() -> str:
+ """ GET /api/v1/status
+ Return:
+ - Status OK
+ """
+ return jsonify({"status": "OK"}), 200
+
+
+@app_views.route('/unauthorized', methods=['GET'], strict_slashes=False)
+def unauthorized() -> str:
+ """ GET /api/v1/unauthorized
+ Return:
+ - Unauthorized
+ """
+ abort(401)
+
+
+@app_views.route('/forbidden', methods=['GET'], strict_slashes=False)
+def forbidden() -> str:
+ """ GET /api/v1/forbidden
+ Return:
+ - Forbidden
+ """
+ abort(403)
diff --git a/server-side/api/v1/views/items.py b/server-side/api/v1/views/items.py
new file mode 100644
index 0000000..4028a3a
--- /dev/null
+++ b/server-side/api/v1/views/items.py
@@ -0,0 +1,173 @@
+from api.v1.views import app_views
+from flask import abort, request, jsonify
+from os import getenv
+from api.v1.views.users import save_picture
+
+
+
+
+@app_views.route('/items', methods=['POST'], strict_slashes=False)
+def new_item():
+ """Create a new item"""
+ from models.db import DBStorage
+ db = DBStorage()
+ item_data = {}
+
+ user_id = request.form.get('user_id')
+ item_name = request.form.get('item_name')
+ description = request.form.get('description')
+ price = request.form.get('price')
+ photo1 = request.files.get('photo1')
+ photo2 = request.files.get('photo2')
+ photo3 = request.files.get('photo3')
+ if not user_id:
+ abort(400, "Missing user_id")
+ if not item_name:
+ abort(400, "Missing item name")
+ if not description:
+ abort(400, "Missing description")
+ if not price:
+ abort(400, "Missing price")
+ if not photo1:
+ abort(400, "Missing photo1")
+
+ pics_fn1 = save_picture(photo1)
+ pics_fn2 = save_picture(photo2)
+ pics_fn3 = save_picture(photo3)
+
+ item_data['user_id'] = user_id
+ item_data['item_name'] = item_name
+ item_data['description'] = description
+ item_data['price'] = price
+ item_data['photo1'] = pics_fn1
+ item_data['photo2'] = pics_fn2
+ item_data['photo3'] = pics_fn3
+
+ try:
+ with db:
+ auth_user = request.current_user
+ if auth_user is not None:
+ if auth_user.user_id != item_data['user_id']:
+ return jsonify({"error": "Unauthorized"}), 401
+ item = db.create_item(item_data)
+ print(f"Item successfully created for {item_data['user_id']}")
+ db.save()
+ return jsonify(item.to_dict()), 201
+ except Exception as e:
+ print(e)
+ abort(400)
+
+
+@app_views.route('/items', methods=['GET'], strict_slashes=False)
+def retreive_items():
+ """Get all items"""
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ items = db.get_items()
+ item_data = [item.to_dict() for item in items]
+ return jsonify(item_data), 200
+ except Exception as e:
+ print(e)
+
+
+@app_views.route('/items/', methods=['GET'], strict_slashes=False)
+def get_items_by_user_id(user_id):
+ """
+ - Get all items by user_id
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ items = db.get_items_by_user_id(user_id)
+ items_data = [item.to_dict() for item in items]
+ return jsonify(items_data), 200
+ except Exception as e:
+ abort(400)
+
+
+@app_views.route('/item/', methods=['GET'], strict_slashes=False)
+def get_item_by_item_id(item_id):
+ """
+ - Get item by item_id
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ items = db.find_items_by_item_id(item_id)
+ if items is None:
+ abort(404)
+ item_data = items.to_dict()
+ return jsonify(item_data), 200
+ except Exception as e:
+ print(e)
+ abort(400)
+
+
+@app_views.route('/items/', methods=['PUT'], strict_slashes=False)
+def update_item_by_item_id(item_id):
+ """
+ - Update item by item_id
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ item_data = {}
+
+ item_name = request.form.get('item_name')
+ description = request.form.get('description')
+ price = request.form.get('price')
+ photo1 = request.files.get('photo1')
+ photo2 = request.files.get('photo2')
+ photo3 = request.files.get('photo3')
+
+ pic_fn1 = save_picture(photo1)
+ pic_fn2 = save_picture(photo2)
+ pic_fn3 = save_picture(photo3)
+
+ item_data['item_name'] = item_name
+ item_data['description'] = description
+ item_data['price'] = price
+ item_data['photo1'] = pic_fn1
+ item_data['photo2'] = pic_fn2
+ item_data['photo3'] = pic_fn3
+
+
+ try:
+ with db:
+ item = db.find_items_by_item_id(item_id)
+ if item is None:
+ abort(404)
+ item.item_name = item_data['item_name']
+ item.description = item_data['description']
+ item_data.price = item_data['price']
+ item_data.photo1 = item_data['photo1']
+ item_data.photo2 = item_data['photo2']
+ item_data.photo3 = item_data['photo3']
+ print(f"Item successfully updated for {item_data['user_id']}")
+ db.save()
+ return jsonify({}), 200
+ except Exception as e:
+ print(e)
+ return jsonify({}), 400
+
+
+@app_views.route('/items/', methods=['DELETE'], strict_slashes=False)
+def delete_item_by_item_id(item_id):
+ """
+ - Delete item by item_id
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ item = db.find_items_by_item_id(item_id)
+ if item is None:
+ abort(404)
+ db.delete(item)
+ db.save()
+ return jsonify({}), 200
+ except Exception as e:
+ abort(400)
diff --git a/server-side/api/v1/views/landscape.jpg b/server-side/api/v1/views/landscape.jpg
new file mode 100644
index 0000000..75631f9
Binary files /dev/null and b/server-side/api/v1/views/landscape.jpg differ
diff --git a/server-side/api/v1/views/likes.py b/server-side/api/v1/views/likes.py
new file mode 100644
index 0000000..45655fe
--- /dev/null
+++ b/server-side/api/v1/views/likes.py
@@ -0,0 +1,73 @@
+from flask import abort, request, jsonify
+from os import getenv
+from collections import Counter
+from api.v1.views import app_views
+
+
+@app_views.route('/likes/', methods=['POST'], strict_slashes=False)
+def likes(status):
+ """Create a new like"""
+ from models.db import DBStorage
+ db = DBStorage()
+ like_data = {}
+
+ get_user_id = request.form.get('user_id')
+ get_item_id = request.form.get('item_id')
+ if not get_user_id:
+ abort(400, "Missing user_id")
+ if not get_item_id:
+ abort(400, "Missing item_id")
+
+ like_data['user_id'] = get_user_id
+ like_data['item_id'] = get_item_id
+ like_data['liked'] = status
+
+ try:
+ with db:
+ like_user = db.get_user_like(like_data['item_id'], like_data['user_id'])
+ auth_user = request.current_user
+ if not auth_user:
+ abort(401)
+ if auth_user.user_id != like_data['user_id']:
+ abort(401)
+ if not like_user:
+ new_like = db.create_like(like_data)
+ db.save()
+ if not new_like:
+ abort(400, "Error creating like")
+ return jsonify(new_like.to_dict()), 201
+ else:
+ if like_user.liked == status:
+ like_user.liked = 1 - status
+ else:
+ like_user.liked = status
+ db.save()
+ return jsonify(like_user.to_dict()), 200
+ except Exception as e:
+ print(e)
+ abort(401)
+
+
+@app_views.route('likes/', methods=['GET'], strict_slashes=False)
+def get_likes(item_id):
+ """Get likes by item_id"""
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ count = 0
+ likes = db.get_like_by_item_id_all(item_id)
+ if not likes:
+ return jsonify([])
+ all_likes = [like.to_dict() for like in likes]
+ c = Counter()
+ for count in all_likes:
+ c[count['liked']] += 1
+ response = {
+ 'liked': c[True],
+ 'dislike': c[False]
+ }
+ return jsonify(response), 200
+ except Exception as e:
+ print(e)
+ abort(400)
\ No newline at end of file
diff --git a/server-side/api/v1/views/passport.jpg b/server-side/api/v1/views/passport.jpg
new file mode 100644
index 0000000..7921218
Binary files /dev/null and b/server-side/api/v1/views/passport.jpg differ
diff --git a/server-side/api/v1/views/ratings.py b/server-side/api/v1/views/ratings.py
new file mode 100644
index 0000000..90e9da9
--- /dev/null
+++ b/server-side/api/v1/views/ratings.py
@@ -0,0 +1,56 @@
+from flask import abort, request, jsonify
+from dotenv import load_dotenv
+from os import getenv
+from api.v1.views import app_views
+
+load_dotenv()
+
+
+@app_views.route('/ratings', methods=['POST'], strict_slashes=False)
+def create_rating():
+ """Create a new rating"""
+ from models.db import DBStorage
+ db = DBStorage()
+ rating_data = {}
+
+ new_user_id = request.form.get('user_id')
+ user_comment = request.form.get('comment')
+ user_rating = request.form.get('rating')
+ if new_user_id is None:
+ abort(400, "Missing user_id")
+ if user_comment is None:
+ abort(400, "Missing comment")
+ if user_rating is None:
+ abort(400, "Missing rating")
+
+ rating_data['user_id'] = new_user_id
+ rating_data['comment'] = user_comment
+ rating_data['rating'] = user_rating
+
+ try:
+ with db:
+ auth_user = request.current_user
+ if auth_user is not None:
+ if auth_user.user_id != rating_data['user_id']:
+ return jsonify({"error": "Unauthorized"}), 401
+ rating = db.create_rating(rating_data)
+ print(f"Rating successfully created for {rating_data['user_id']}")
+ db.save()
+ return jsonify(rating.to_dict()), 201
+ except Exception as e:
+ print(e)
+ abort(400)
+
+
+@app_views.route('/ratings', methods=['GET'], strict_slashes=False)
+def get_all_ratings():
+ """Get all ratings"""
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ ratings = db.get_all_ratings()
+ ratings_data = [rating.to_dict() for rating in ratings]
+ return jsonify(ratings_data), 200
+ except Exception as e:
+ abort(400)
diff --git a/server-side/api/v1/views/users.py b/server-side/api/v1/views/users.py
new file mode 100644
index 0000000..6868ebc
--- /dev/null
+++ b/server-side/api/v1/views/users.py
@@ -0,0 +1,403 @@
+"""
+Modules for users endpoints
+"""
+from api.v1.views import app_views
+from api.v1.views import auth
+from flask import abort, jsonify, request, abort, render_template, redirect, url_for, flash, session
+import os
+from uuid import uuid4
+from bcrypt import hashpw, gensalt, checkpw
+import cloudinary
+from cloudinary.uploader import upload
+from dotenv import load_dotenv
+from datetime import datetime, timedelta
+
+load_dotenv()
+
+CLOUDNAME = os.getenv("CLOUD_NAME")
+API_KEY = os.getenv("API_KEY")
+API_SECRET = os.getenv("API_SECRET")
+
+cloudinary.config(
+ cloud_name=CLOUDNAME,
+ api_key=API_KEY,
+ api_secret=API_SECRET
+)
+
+
+@app_views.route('/users', methods=['GET'], strict_slashes=False)
+def get_users():
+ """ GET /api/v1/users
+ Return:
+ - All users
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ users = db.find_all_users()
+ users_data = [{"user_id": user.user_id, "fullname": user.fullname,
+ "email": user.email, "verified": user.verified, "gender": user.gender,
+ "phone1": user.phone1, "about": user.about, "address": user.address,
+ "city": user.city, "town": user.town, "state": user.state,
+ "created_at": user.created_at, "updated_at": user.updated_at} for user in users]
+ return jsonify(users_data), 200
+ except Exception as e:
+ abort(404)
+
+
+@app_views.route('/users/', methods=['GET'], strict_slashes=False)
+def get_user_by_id(user_id):
+ """
+ - Get user based on user_id
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+
+ # Check if user_id is None
+ try:
+ if user_id is None:
+ abort(404)
+ if user_id == 'me':
+ if request.current_user is None:
+ abort(404)
+ user = request.current_user
+ user_data = {"user_id": user.user_id, "fullname": user.fullname,
+ "about": user.about, "address": user.address,
+ "email": user.email, "verified": user.verified, "gender": user.gender,
+ "city": user.city, "town": user.town, "state": user.state,
+ "created_at": user.created_at, "updated_at": user.updated_at}
+ return jsonify(user_data), 200
+ with db:
+ auth_user = request.current_user
+ user = db.find_user_by_id(user_id)
+ if user is None:
+ abort(404)
+ if auth_user is not None:
+ if auth_user.user_id != user.user_id:
+ return jsonify({"error": "Unauthorized"}), 401
+ db.save()
+ return jsonify(user.to_dict()), 200
+ except Exception as e:
+ # Log the exception for debugging purposes
+ print(f"Error: {e}")
+ # Return an error response
+ return jsonify({"error": "Not Found"}), 404
+
+
+@app_views.route('/users', methods=['POST'], strict_slashes=False)
+def post_user():
+ """
+ - Post a new user
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ user_data = {}
+
+ user_fullname = request.form.get('fullname')
+ user_verified = request.form.get('verified')
+ user_email = request.form.get('email')
+ user_password = request.form.get('password')
+ user_gender = request.form.get('gender')
+ user_phone1 = request.form.get('phone1')
+ user_phone2 = request.form.get('phone2')
+ user_about = request.form.get('about')
+ user_address = request.form.get('address')
+ user_town = request.form.get('town')
+ user_city = request.form.get('city')
+ user_state = request.form.get('state')
+
+ if user_fullname is None:
+ return jsonify({"error": "fullname missing"}), 400
+ if user_email is None:
+ return jsonify({"error": "email missing"}), 400
+ if user_password is None:
+ return jsonify({"error": "password missing"}), 400
+ if user_gender is None:
+ return jsonify({"error": "gender missing"}), 400
+ if user_phone1 is None:
+ return jsonify({"error": "phone1 missing"}), 400
+ if user_about is None:
+ return jsonify({"error": "about missing"}), 400
+ if user_address is None:
+ return jsonify({"error": "address missing"}), 400
+ if user_town is None:
+ return jsonify({"error": "town missing"}), 400
+ if user_city is None:
+ return jsonify({"error": "city missing"}), 400
+ if user_state is None:
+ return jsonify({"error": "state missing"}), 400
+ user_data['fullname'] = user_fullname
+ user_data['email'] = user_email
+ user_data['verified'] = user_verified
+ user_data['password'] = user_password
+ user_data['gender'] = user_gender
+ user_data['phone1'] = user_phone1
+ user_data['phone2'] = user_phone2
+ user_data['about'] = user_about
+ user_data['address'] = user_address
+ user_data['town'] = user_town
+ user_data['city'] = user_city
+ user_data['state'] = user_state
+ try:
+ with db:
+ hashed_password = hashpw(
+ user_data['password'].encode('utf-8'), gensalt())
+ user_data['password'] = hashed_password
+ user = db.create_user(user_data)
+ print(f"User created successfully for {user.fullname}")
+ db.save()
+ return jsonify(user.to_dict()), 201
+ except Exception as e:
+ abort(404)
+
+
+@app_views.route('/users/login', methods=['POST'], strict_slashes=False)
+def login_user():
+ """
+ - Login a user
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ user_email = request.form.get('email')
+ user_password = request.form.get('password')
+ if user_email is None:
+ return jsonify({"error": "email missing"}), 400
+ if user_password is None:
+ return jsonify({"error": "password missing"}), 400
+ with db:
+ user = db.find_user_by_email(user_email)
+ if user is None:
+ return jsonify({"error": "Invalid login details"}), 401
+ if not checkpw(user_password.encode('utf-8'), user.password.encode('utf-8')):
+ return jsonify({"error": "Invalid login details"}), 401
+ from api.v1.auth.session_db_auth import SessionDBAuth
+ session_auth = SessionDBAuth()
+ session_id = session_auth.create_session(user.user_id)
+ response = jsonify(user.to_dict())
+ cookie_name = os.getenv('SESSION_NAME')
+ response.set_cookie(cookie_name, session_id)
+ db.save()
+ return response, 200
+ except Exception as e:
+ abort(404)
+
+
+@auth.route('/signup', methods=['GET'], strict_slashes=False)
+def signup():
+ """
+ - Login a user
+ """
+ return render_template('register.html')
+
+
+@auth.route('/login', methods=['GET'], strict_slashes=False)
+def login_page():
+ """
+ - Login a user
+ """
+ return render_template('login.html')
+
+
+@auth.route('/home', methods=['GET'], strict_slashes=False)
+def get_home():
+ """
+ - fetch home page
+ """
+ return render_template('index.html')
+
+
+@auth.route('/user/login', methods=['POST'], strict_slashes=False)
+def login_user():
+ """
+ - Login a user
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ user_email = request.form.get('email')
+ user_password = request.form.get('password')
+ if user_email is None:
+ flash("Email is missing", 'danger')
+ return redirect(url_for('auth.login_page'))
+ if user_password is None:
+ flash("Password is missing", 'danger')
+ return redirect(url_for('auth.login_page'))
+ with db:
+ user = db.find_user_by_email(user_email)
+ if user is None or not checkpw(user_password.encode('utf-8'), user.password.encode('utf-8')):
+ flash("Invalid login details!", 'danger')
+ return redirect(url_for('auth.login_page'))
+ from api.v1.auth.session_db_auth import SessionDBAuth
+ session_auth = SessionDBAuth()
+ session_id = session_auth.create_session(user.user_id)
+ response = redirect(url_for('auth.get_home'))
+ cookie_name = os.getenv('SESSION_NAME')
+ response.set_cookie(cookie_name, session_id)
+ db.save()
+ return response
+ except Exception as e:
+ flash("An error occurred", 'error')
+ return redirect(url_for('auth.login_page'))
+
+
+@app_views.route('/users/logout', methods=['POST'], strict_slashes=False)
+def logout_user():
+ """
+ - Logout a user
+ """
+ try:
+ from api.v1.auth.session_db_auth import SessionDBAuth
+ session_auth = SessionDBAuth()
+ session_auth.destroy_session(request)
+ return jsonify({}), 200
+ except Exception as e:
+ abort(404)
+
+
+@app_views.route('/users/', methods=['POST'], strict_slashes=False)
+def put_user(user_id):
+ """
+ - Update a user
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ user_email = request.form.get('email')
+ user_password = request.form.get('password')
+ new_password = request.form.get('new_password')
+
+ if user_email is None:
+ return jsonify({"error": "email missing"}), 400
+ if user_password is None:
+ return jsonify({"error": "password missing"}), 400
+ if new_password is None:
+ return jsonify({"error": "new_password missing"}), 400
+ with db:
+ auth_user = request.current_user
+ user = db.find_user_by_id(user_id)
+ if user is None:
+ abort(404)
+ if not checkpw(user_password.encode('utf-8'), user.password.encode('utf-8')):
+ return jsonify({"error": "wrong password"}), 401
+ if auth_user is not None:
+ if auth_user.user_id != user.user_id:
+ return jsonify({"error": "Unauthorized"}), 401
+ hashed_password = hashpw(new_password.encode('utf-8'), gensalt())
+ user.password = hashed_password
+ db.save()
+ return jsonify(user.to_dict()), 201
+ except Exception as e:
+ abort(401)
+
+
+@app_views.route('/users/', methods=['DELETE'], strict_slashes=False)
+def delete_user(user_id):
+ """
+ - Delete a user
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ try:
+ with db:
+ auth_user = request.current_user
+ user = db.find_user_by_id(user_id)
+ sess = db.find_session_by_id_by_user_id(user_id)
+
+ if user is None:
+ return jsonify({"error": "User not found"}), 404
+ if sess is None:
+ return jsonify({"error": "No session found for that user"}), 404
+ if auth_user is not None:
+ if auth_user.user_id != user.user_id:
+ return jsonify({"error": "Unauthorized"}), 401
+
+ db.delete_session(sess)
+ db.delete_user(user)
+ db.save()
+ return jsonify({}), 204 # 204 No Content for successful deletion
+ except Exception as e:
+ return jsonify({"error": "Internal Server Error"}), 500
+
+
+def save_picture(form_picture):
+ """
+ - Save the picture
+ """
+ try:
+ pics_id = str(uuid4())
+ _, f_ext = os.path.splitext(form_picture.filename)
+
+ allowed_extensions = {'.png', '.jpg', '.jpeg'}
+
+ if f_ext.lower() in allowed_extensions:
+ picture_fn = pics_id + f_ext
+
+ # Upload image to cloudinary
+ res = upload(form_picture, public_id=picture_fn)
+
+ # Extract the public ID from the Cloudinary response
+ cloudinary_public_id = res['public_id']
+
+ # We return the public ID incase we want to store it in the db
+ return cloudinary_public_id
+ else:
+ return None
+ except Exception as e:
+ print(e)
+
+
+@app_views.route('/users/', methods=['PUT'], strict_slashes=False)
+def update_user_by_user_id(user_id):
+ """
+ - Update a user
+ """
+ from models.db import DBStorage
+ db = DBStorage()
+ user_data = {}
+
+ user_fullname = request.form.get('fullname')
+ user_phone1 = request.form.get('phone1')
+ user_phone2 = request.form.get('phone2')
+ user_about = request.form.get('about')
+ user_address = request.form.get('address')
+ user_town = request.form.get('town')
+ user_city = request.form.get('city')
+ user_state = request.form.get('state')
+ user_photo = request.files.get('photo')
+
+ pics_fn = save_picture(user_photo)
+
+ user_data['fullname'] = user_fullname
+ user_data['phone1'] = user_phone1
+ user_data['phone2'] = user_phone2
+ user_data['about'] = user_about
+ user_data['address'] = user_address
+ user_data['town'] = user_town
+ user_data['city'] = user_city
+ user_data['state'] = user_state
+ user_data['photo'] = pics_fn
+ try:
+ with db:
+ auth_user = request.current_user
+ user = db.find_user_by_id(user_id)
+ if user is None:
+ abort(404)
+ if auth_user is not None:
+ if auth_user.user_id != user.user_id:
+ return jsonify({"error": "Unauthorized"}), 401
+ user.about = user_data['about']
+ user.address = user_data['address']
+ user.city = user_data['city']
+ user.town = user_data['town']
+ user.state = user_data['state']
+ user.fullname = user_data['fullname']
+ user.phone1 = user_data['phone1']
+ user.phone2 = user_data['phone2']
+ user.photo = user_data['photo']
+ db.save()
+ return jsonify(user.to_dict()), 200
+ except Exception as e:
+ print(e)
+ abort(404)
diff --git a/server-side/models/__init__.py b/server-side/models/__init__.py
new file mode 100644
index 0000000..d3f5a12
--- /dev/null
+++ b/server-side/models/__init__.py
@@ -0,0 +1 @@
+
diff --git a/server-side/models/db.py b/server-side/models/db.py
new file mode 100644
index 0000000..db824be
--- /dev/null
+++ b/server-side/models/db.py
@@ -0,0 +1,412 @@
+from os import getenv
+from dotenv import load_dotenv
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker, scoped_session
+from models.tables import Base
+from sqlalchemy.pool import QueuePool
+from sqlalchemy import inspect
+
+load_dotenv()
+
+database = getenv("dbName")
+user = getenv("dbUser")
+host = getenv("dbHost")
+password = getenv("dbPasswd")
+port = getenv("dbPort")
+
+
+def create_engine_with_session():
+ """Create SQLAlchemy engine and scoped session."""
+ engine = create_engine(
+ f'mysql+mysqldb://{user}:{password}@{host}:{port}/{database}', pool_pre_ping=True, poolclass=QueuePool, pool_size=5, max_overflow=10)
+ session_factory = sessionmaker(bind=engine, expire_on_commit=False)
+ Session = scoped_session(session_factory)
+ return engine, Session
+
+
+class DBStorage:
+ def __init__(self):
+ """Initialize the DBStorage instance."""
+ self.__engine, self.__session = create_engine_with_session()
+ Base.metadata.create_all(self.__engine)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
+ def all(self, cls=None):
+ """Return a dictionary of all objects of a given class."""
+ objects = {}
+ for name, class_ in inspect.getmembers(Base.classes):
+ if cls is None or cls == class_:
+ objs = self.__session.query(class_).all()
+ for obj in objs:
+ key = f'{type(obj).__name__}.{obj.id}'
+ objects[key] = obj
+ return objects
+
+ def new(self, obj):
+ """Add a new object to the session."""
+ if obj:
+ self.__session.add(obj)
+
+ def save(self):
+ """Commit changes to the database."""
+ try:
+ self.__session.commit()
+ except Exception as e:
+ print(f"Error during save: {e}")
+ self.__session.rollback()
+ raise e
+
+ def delete(self, obj=None):
+ """Delete an object from the session."""
+ try:
+ if obj:
+ self.__session.delete(obj)
+ except Exception as e:
+ print(f"Error during delete: {e}")
+ self.__session.rollback()
+ raise e
+
+ def reload(self):
+ """Create all tables and reset the session."""
+ Base.metadata.create_all(self.__engine)
+ self.__session = scoped_session(sessionmaker(
+ bind=self.__engine, expire_on_commit=False))
+
+ def close(self):
+ """Close the session."""
+ self.__session.close()
+
+ def find_user_by_email(self, email):
+ """Find a user by email."""
+ from models.tables import User
+ try:
+ return self.__session.query(User).filter_by(email=email).first()
+ except Exception as e:
+ print(f"Error during find_user_by_email: {e}")
+ return None
+
+ def find_user_by_id(self, user_id):
+ """Find a user by id."""
+ from models.tables import User
+ try:
+ return self.__session.query(User).filter_by(user_id=user_id).first()
+ except Exception as e:
+ print(f"Error during find_user_by_id: {e}")
+ return None
+
+ def find_all_users(self):
+ """Find all users."""
+ from models.tables import User
+ try:
+ return self.__session.query(User).all()
+ except Exception as e:
+ print(f"Error during find_all_users: {e}")
+ return None
+
+ def create_user(self, user_data):
+ """Create a user."""
+ from models.tables import User
+ try:
+ user = User(**user_data)
+ self.new(user)
+ self.save()
+ return user
+ except Exception as e:
+ print(f"Error during create_user: {e}")
+ return None
+
+ def create_session(self, session_data):
+ """Create a session."""
+ from models.tables import UserSession
+ try:
+ session = UserSession(**session_data)
+ self.new(session)
+ self.save()
+ return session
+ except Exception as e:
+ print(f"Error during create_session: {e}")
+ return None
+
+ def find_session_by_id(self, session_id):
+ """Find a session by id."""
+ from models.tables import UserSession
+ try:
+ return self.__session.query(UserSession).filter_by(session_id=session_id).first()
+ except Exception as e:
+ print(f"Error during find_session_by_id: {e}")
+ return None
+
+ def find_session_by_id_by_user_id(self, user_id):
+ """Find a session by user_id."""
+ from models.tables import UserSession
+ try:
+ return self.__session.query(UserSession).filter_by(user_id=user_id).first()
+ except Exception as e:
+ print(f"Error during find_session_by_id: {e}")
+ return None
+
+ def delete_session(self, session):
+ """Delete a session."""
+ from models.tables import UserSession
+ try:
+ self.delete(session)
+ self.save()
+ except Exception as e:
+ print(f"Error during delete_session: {e}")
+
+ def delete_user(self, user):
+ """Delete a user."""
+ from models.tables import User
+ try:
+ self.delete(user)
+ self.save()
+ except Exception as e:
+ print(f"Error during delete_user: {e}")
+
+ def create_item(self, item_data):
+ """Create an item."""
+ from models.tables import Item
+ try:
+ item = Item(**item_data)
+ self.new(item)
+ self.save()
+ return item
+ except Exception as e:
+ print(f"Error during create_item: {e}")
+ return None
+
+ def get_items(self):
+ """Get all items."""
+ from models.tables import Item
+ try:
+ return self.__session.query(Item).all()
+ except Exception as e:
+ print(f"Error during get_items: {e}")
+ return None
+
+ def get_items_by_user_id(self, user_id):
+ """
+ - Retreive all items by user_id
+ """
+ from models.tables import Item
+ try:
+ return self.__session.query(Item).filter_by(user_id=user_id).all()
+ except Exception as e:
+ print(f"Error during get_items_by_user_id: {e}")
+ return None
+
+ def item_by_item_id(self, item_id):
+ """
+ - Retreive an item by item_id
+ """
+ from models.tables import Item
+ try:
+ return self.__session.query(Item).filter_by(item_id=item_id).first()
+ except Exception as e:
+ print(f"Error during get_item_by_item_id: {e}")
+ return None
+
+ def find_items_by_item_id(self, item_id):
+ """
+ - Retreive an item by item_id
+ """
+ from models.tables import Item
+ try:
+ return self.__session.query(Item).filter_by(item_id=item_id).first()
+ except Exception as e:
+ print(f"Error during get_item_by_item_id: {e}")
+ return None
+
+ def update_item_by_item_id(self, item_id, item_data):
+ """
+ - Update an item by item_id
+ """
+ from models.tables import Item
+ try:
+ item = self.__session.query(
+ Item).filter_by(item_id=item_id).first()
+ for key, value in item_data.items():
+ setattr(item, key, value)
+ self.save()
+ return item
+ except Exception as e:
+ print(f"Error during update_item_by_item_id: {e}")
+ return None
+
+ def delete_item_by_item_id(self, item_id):
+ """
+ - Delete an item by item_id
+ """
+ from models.tables import Item
+ try:
+ item = self.__session.query(
+ Item).filter_by(item_id=item_id).first()
+ self.delete(item)
+ self.save()
+ except Exception as e:
+ print(f"Error during delete_item_by_item_id: {e}")
+ return None
+
+ def create_rating(self, rating_data):
+ """
+ - Create a rating
+ """
+ from models.tables import Rating
+ try:
+ rating = Rating(**rating_data)
+ self.new(rating)
+ self.save()
+ return rating
+ except Exception as e:
+ print(f"Error during create_rating: {e}")
+ return None
+
+ def get_all_ratings(self):
+ """
+ - Retreive all ratings
+ """
+ from models.tables import Rating
+ try:
+ return self.__session.query(Rating).all()
+ except Exception as e:
+ print(f"Error during get_all_ratings: {e}")
+ return None
+
+ def get_ratings_by_user_id(self, user_id):
+ """
+ - Retrieve ratings based on user_id
+ """
+ from models.tables import Rating
+ try:
+ return self.__session.query(Rating).filter_by(user_id=user_id).first()
+ except Exception as e:
+ print(f"Error during retriving ratings: {e}")
+ return None
+
+ def create_like(self, like_data):
+ """
+ - Create a like
+ """
+ from models.tables import Like
+ try:
+ like = Like(**like_data)
+ self.new(like)
+ self.save()
+ return like
+ except Exception as e:
+ print(f"Error during create_like: {e}")
+ return None
+
+ def get_like_by_item_id(self, item_id):
+ """
+ - Retrieve like by item_id
+ """
+ from models.tables import Like
+ try:
+ return self.__session.query(Like).filter_by(item_id=item_id).first()
+ except Exception as e:
+ print(f"Error during get_like_by_user_id: {e}")
+ return None
+
+
+ def get_user_like(self, item_id, user_id):
+ """
+ get a specified user like
+ """
+ from models.tables import Like
+ try:
+ results = self.__session.query(Like).filter_by(item_id=item_id).all()
+ for result in results:
+ if result.user_id == user_id:
+ return result
+ except Exception as e:
+ print(f"Error during error fetching like data: {e}")
+ return None
+
+ def get_like_by_item_id_all(self, item_id):
+ """
+ - Retrieve like by item_id
+ """
+ from models.tables import Like
+ try:
+ return self.__session.query(Like).filter_by(item_id=item_id).all()
+ except Exception as e:
+ print(f"Error during get_like_by_user_id: {e}")
+ return None
+
+ def create_comment(self, comment_data):
+ """
+ - Create a comment
+ """
+ from models.tables import Comment
+ try:
+ comment = Comment(**comment_data)
+ self.new(comment)
+ self.save()
+ return comment
+ except Exception as e:
+ print(f"Error during create_comment: {e}")
+ return None
+
+ def get_comments_by_comment_id(self, comment_id):
+ """
+ - Retrieve comment by comment_id
+ """
+ from models.tables import Comment
+ try:
+ return self.__session.query(Comment).filter_by(comment_id=comment_id).first()
+ except Exception as e:
+ print(f"Error during get_comment: {e}")
+ return None
+
+ def get_comments_by_item_id(self, item_id):
+ """
+ - Retrieve comment by item_id
+ """
+ from models.tables import Comment
+ try:
+ return self.__session.query(Comment).filter_by(item_id=item_id).all()
+ except Exception as e:
+ print(f"Error during get_comment: {e}")
+ return None
+
+ def create_message(self, message_data):
+ """
+ - Create new message
+ """
+ from models.tables import Chat
+ try:
+ message = Chat(**message_data)
+ self.new(message)
+ self.save()
+ return message
+ except Exception as e:
+ print(f"Error during create_message: {e}")
+ return None
+
+ def get_messages_by_message_id(self, message_id):
+ """
+ - Retreive a message based on their messsage_id
+ """
+ from models.tables import Chat
+ try:
+ return self.__session.query(Chat).filter_by(message_id=message_id).first()
+ except Exception as e:
+ print(f"Error during get_messages: {e}")
+ return None
+
+ def get_messages_by_user_id(self, sender_id):
+ """
+ - Retreive all messages based on their user_id
+ """
+ from models.tables import Chat
+ try:
+ return self.__session.query(Chat).filter_by(sender_id=sender_id).all()
+ except Exception as e:
+ print(f"Error during get_messages: {e}")
+ return None
diff --git a/server-side/models/tables.py b/server-side/models/tables.py
new file mode 100644
index 0000000..b8e78c9
--- /dev/null
+++ b/server-side/models/tables.py
@@ -0,0 +1,238 @@
+from uuid import uuid4
+import datetime
+from sqlalchemy import Column, String, Boolean, Enum, Text, TIMESTAMP, Index, Integer, ForeignKey, CheckConstraint, DateTime
+from sqlalchemy.sql import func
+from sqlalchemy.orm import relationship, DeclarativeBase
+import os
+
+
+class Base(DeclarativeBase):
+ """Base class for all models"""
+ pass
+
+
+class BaseModel:
+ """Base Model class"""
+ created_at = Column(DateTime, nullable=False,
+ default=datetime.datetime.utcnow())
+ updated_at = Column(DateTime, nullable=False,
+ default=datetime.datetime.utcnow(), onupdate=datetime.datetime.utcnow())
+
+
+class User(BaseModel, Base):
+ """User class"""
+ __tablename__ = 'users'
+ user_id = Column(String(60), nullable=False,
+ primary_key=True, default=lambda: str(uuid4()))
+ fullname = Column(String(30), nullable=False)
+ verified = Column(Boolean, nullable=False, default=False)
+ email = Column(String(225), nullable=False, unique=True)
+ password = Column(String(225), nullable=False)
+ gender = Column(Enum('Male', 'Female', 'Other'), nullable=False)
+ phone1 = Column(String(15), nullable=False)
+ phone2 = Column(String(15))
+ about = Column(Text)
+ address = Column(String(50), nullable=False)
+ town = Column(String(30), nullable=False)
+ city = Column(String(30), nullable=False)
+ state = Column(String(20), nullable=False)
+ photo = Column(String(225), nullable=False, default=os.path.join(os.path.dirname(__file__), '..', '..', 'client-side', 'assets', 'default.png'))
+ items = relationship('Item', back_populates='user', cascade='all, delete-orphan')
+ ratings = relationship('Rating', back_populates='user', cascade='all, delete-orphan')
+ likes = relationship('Like', back_populates='user', cascade='all, delete-orphan')
+ comments = relationship('Comment', back_populates='user', cascade='all, delete-orphan')
+ # Define the sent_messages relationship
+ sent_messages = relationship('Chat', foreign_keys='Chat.sender_id',
+ back_populates='sender', cascade='all, delete-orphan')
+
+ # Define the received_messages relationship
+ received_messages = relationship(
+ 'Chat', foreign_keys='Chat.receiver_id', back_populates='receiver', cascade='all, delete-orphan')
+
+ __table_args__ = (Index('idx_users_user_id', 'user_id'),)
+
+ def to_dict(self):
+ """Convert user object to dictionary"""
+ user_dict = {
+ 'user_id': self.user_id,
+ 'fullname': self.fullname,
+ 'verified': self.verified,
+ 'email': self.email,
+ 'gender': self.gender,
+ 'phone1': self.phone1,
+ 'phone2': self.phone2,
+ 'about': self.about,
+ 'address': self.address,
+ 'town': self.town,
+ 'city': self.city,
+ 'state': self.state,
+ }
+ return user_dict
+
+
+class Item(BaseModel, Base):
+ """Item class"""
+ __tablename__ = 'items'
+ item_id = Column(String(36), nullable=False, primary_key=True,
+ default=lambda: str(uuid4()))
+ user_id = Column(String(36), ForeignKey('users.user_id', ondelete='CASCADE'),
+ nullable=False)
+ item_name = Column(String(20), nullable=False)
+ price = Column(Integer, nullable=False)
+ description = Column(String(200), nullable=False)
+ photo1 = Column(String(225), nullable=False)
+ photo2 = Column(String(225))
+ photo3 = Column(String(225))
+ sold = Column(Boolean, nullable=False, default=False)
+ created_at = Column(TIMESTAMP, default=func.now(), nullable=False)
+ updated_at = Column(TIMESTAMP, default=func.now(),
+ onupdate=func.now(), nullable=False)
+
+ # Define a relationship with the User class
+ user = relationship('User', back_populates='items')
+ likes = relationship('Like', back_populates='items')
+ comments = relationship('Comment', back_populates='items')
+
+ __table_args__ = (Index('idx_items_item_id', 'item_id'),)
+
+ def to_dict(self):
+ """Convert item object to dictionary"""
+ item_dict = {
+ 'user_id': self.user_id,
+ 'item_name': self.item_name,
+ 'description': self.description,
+ 'price': self.price,
+ 'photo1': self.photo1,
+ 'photo2': self.photo2,
+ 'photo3': self.photo3,
+ 'sold': self.sold,
+ }
+ return item_dict
+
+
+class Comment(BaseModel, Base):
+ """Comment class"""
+ __tablename__ = 'comments'
+ comment_id = Column(String(36), nullable=False, primary_key=True,
+ default=lambda: str(uuid4()))
+ commenter = Column(String(36), ForeignKey(
+ 'users.user_id', ondelete='CASCADE'), nullable=False)
+ item_id = Column(String(36), ForeignKey(
+ 'items.item_id', ondelete='CASCADE'), nullable=False)
+ comment = Column(Text, nullable=False)
+ created_at = Column(TIMESTAMP, default=func.now(), nullable=False)
+ updated_at = Column(TIMESTAMP, default=func.now(),
+ onupdate=func.now(), nullable=False)
+
+ # Define relationships with User and Item classes
+ user = relationship('User', back_populates='comments')
+ items = relationship('Item', back_populates='comments')
+
+ def to_dict(self):
+ """Convert rating object to dictionary"""
+ comment_dict = {
+ 'comment_id': self.comment_id,
+ 'item_id': self.item_id,
+ 'commenter': self.commenter,
+ 'comment': self.comment,
+ 'created_at': self.created_at,
+ }
+ return comment_dict
+
+
+class Chat(BaseModel, Base):
+ """Chat class"""
+ __tablename__ = 'chats'
+ message_id = Column(String(36), nullable=False, primary_key=True,
+ default=lambda: str(uuid4()))
+ sender_id = Column(String(36), ForeignKey('users.user_id', ondelete='CASCADE'),
+ nullable=False)
+ receiver_id = Column(String(36), ForeignKey(
+ 'users.user_id', ondelete='CASCADE'), nullable=False)
+ message = Column(Text, nullable=False)
+ created_at = Column(TIMESTAMP, default=func.now(), nullable=False)
+ updated_at = Column(TIMESTAMP, default=func.now(),
+ onupdate=func.now(), nullable=False)
+
+ # Define relationships with User classes
+ sender = relationship('User', foreign_keys=[
+ sender_id], back_populates='sent_messages')
+ receiver = relationship('User', foreign_keys=[
+ receiver_id], back_populates='received_messages')
+
+ def to_dict(self):
+ """
+ - Convert chat object to dictionary
+ """
+ chat_data = {
+ 'message_id': self.message_id,
+ 'sender_id': self.sender_id,
+ 'receiver_id': self.receiver_id,
+ 'message': self.message,
+ 'created_at': self.created_at,
+ }
+ return chat_data
+
+
+class Like(BaseModel, Base):
+ """Like class"""
+ __tablename__ = 'likes'
+
+ item_id = Column(String(36), ForeignKey('items.item_id', ondelete='CASCADE'),
+ nullable=False, primary_key=True)
+ user_id = Column(String(36), ForeignKey('users.user_id', ondelete='CASCADE'),
+ nullable=False, primary_key=True)
+ liked = Column(Boolean, nullable=False)
+ created_at = Column(TIMESTAMP, default=func.now(), nullable=False)
+ updated_at = Column(TIMESTAMP, default=func.now(),
+ onupdate=func.now(), nullable=False)
+
+ # Define relationships with User and Item class
+ user = relationship('User', back_populates='likes')
+ items = relationship('Item', back_populates='likes')
+
+ def to_dict(self):
+ """Convert rating object to dictionary"""
+ like_dict = {
+ 'user_id': self.user_id,
+ 'item_id': self.item_id,
+ 'liked': self.liked,
+ 'created_at': self.created_at,
+ }
+ return like_dict
+
+
+class Rating(Base):
+ """Rating class"""
+ __tablename__ = 'ratings'
+
+ rating_id = Column(String(36), primary_key=True,
+ default=lambda: str(uuid4()))
+ user_id = Column(String(36), ForeignKey(
+ 'users.user_id', ondelete='CASCADE'), nullable=False)
+ rating = Column(Enum('1', '2', '3', '4', '5'), nullable=False)
+ comment = Column(String(225))
+ created_at = Column(TIMESTAMP, default=func.now(), nullable=False)
+
+ # Define a relationship with the User class
+ user = relationship('User', back_populates='ratings')
+
+ def to_dict(self):
+ """Convert rating object to dictionary"""
+ user_dict = {
+ 'user_id': self.user_id,
+ 'rating': self.rating,
+ 'comment': self.comment,
+ }
+ return user_dict
+
+
+class UserSession(Base):
+ """
+ UserSession model class
+ """
+ __tablename__ = 'user_sessions'
+
+ user_id = Column(String(60), nullable=False)
+ session_id = Column(String(60), primary_key=True, nullable=False)
+ created_at = Column(TIMESTAMP, default=func.now(), nullable=False)
diff --git a/server-side/requirements.txt b/server-side/requirements.txt
new file mode 100644
index 0000000..589302b
--- /dev/null
+++ b/server-side/requirements.txt
@@ -0,0 +1,10 @@
+Flask==3.0.0
+Flask-Cors==4.0.0
+Jinja2==3.1.2
+mysqlclient==2.1.1
+pycodestyle==2.6.0
+python-dotenv==1.0.0
+requests==2.18.4
+SQLAlchemy==2.0.23
+Werkzeug==3.0.1
+
diff --git a/server-side/tradequick.sql b/server-side/tradequick.sql
index 374d813..ce72981 100644
--- a/server-side/tradequick.sql
+++ b/server-side/tradequick.sql
@@ -1,5 +1,5 @@
-- The MySQL syntax for implementing the database schema for tradeQuick application
--- Create database and use it
+-- Create database and make use of it
CREATE DATABASE IF NOT EXISTS tradequick;
USE tradequick;
@@ -95,9 +95,9 @@ CREATE TABLE
IF NOT EXISTS ratings (
rating_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
- rating INT CHECK (rating > 0 AND rating <= 5) NOT NULL,
+ rating ENUM('1', '2', '3', '4', '5') NOT NULL,
comment VARCHAR(225),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (rating_id),
FOREIGN KEY (user_id) REFERENCES users (user_id)
- );
\ No newline at end of file
+ );