Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/course2 shopping app #7

Merged
merged 4 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
venv
__pycache__
.dockerignore
.git
8 changes: 2 additions & 6 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python package

on:
Expand All @@ -16,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.11.8"]

steps:
- uses: actions/checkout@v3
Expand All @@ -27,7 +24,6 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
Expand All @@ -37,4 +33,4 @@ jobs:
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
python -m pytest
25 changes: 25 additions & 0 deletions ShoppingApp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Use an official Python runtime as a parent image
FROM python:3.9-slim

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy the dependencies file to the working directory
COPY requirements.txt ./

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code to the working directory
COPY . .

# Expose the port the app runs on
EXPOSE 5000

# Define environment variables
ENV FLASK_APP=run.py
ENV FLASK_RUN_HOST=0.0.0.0
ENV FLASK_ENV=development

# Run the application
CMD ["flask", "run"]
27 changes: 20 additions & 7 deletions ShoppingApp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ These instructions will get you a copy of the project up and running on your loc

- Python 3.6 or higher
- pip
- Docker

### Installing

Expand Down Expand Up @@ -43,42 +44,54 @@ A step-by-step series of examples that tell you how to get a development environ

This command starts the Flask development server. By default, the application will be accessible at http://127.0.0.1:5000/.

### Using Docker

Alternatively, you can run the application using Docker. Follow these steps:

1. Pull the Docker image from Docker Hub:

```bash
docker pull sudo-scorpion/shopping-app
```

2. Run the Docker container:

```bash
docker run -p 5000:5000 sudo-scorpion/shopping-app
```

This command starts the Flask application inside a Docker container. By default, the application will be accessible at http://127.0.0.1:5000/.

### Using the Swagger UI

The Flask application is configured with Flask-RESTx, which automatically generates a Swagger UI for the application's API.

Access the Swagger UI: Open a web browser and navigate to http://127.0.0.1:5000/. You'll be presented with the Swagger UI, listing all available API endpoints.

Test the APIs:

- You can expand each API endpoint to see its documentation, required parameters, and the model it expects.
- To test an endpoint, click on the "Try it out" button, fill in the required parameters, and then click "Execute".
- The Swagger UI will display the request as it was sent, the server's response, and the response body.

### Key Endpoints

User Registration and Login:

- `/auth/register` - Register a new user.
- `/auth/login` - Login as an existing user.

Product Management:

- `/products/` - List all products or add a new product.
- `/products/{id}` - Retrieve, update, or delete a specific product.

Category Management:

- `/categories/` - List all categories or add a new category.
- `/categories/{id}` - Retrieve, update, or delete a specific category.

Cart Operations:

- `/cart/` - View the cart or add items to the cart.
- `/cart/{product_id}` - Remove or update quantity of a specific item in the cart.

Checkout:

- `/checkout/` - Process the checkout of the current cart.

### Development Notes
Expand All @@ -92,4 +105,4 @@ Feel free to fork the repository and submit pull requests.

### License

This project is licensed under the MIT License - see the LICENSE.md file for details.
This project is licensed under the MIT License - see the LICENSE.md file for details.
2 changes: 1 addition & 1 deletion ShoppingApp/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

def create_app():
app = Flask(__name__)
api = Api(app, title='Shopping App', version='1.0', description='A simple shopping API')
api = Api(app, title='Shopping App', version='1.0', description='Welcome to the Demo Marketplace')

# Register namespaces
api.add_namespace(auth_ns, path='/auth')
Expand Down
23 changes: 20 additions & 3 deletions ShoppingApp/app/api/namespaces/auth_ns.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from flask_restx import Namespace, Resource, fields, reqparse
from werkzeug.security import generate_password_hash
from app.services.auth_service import add_user, authenticate_user
from flask_restx import Namespace, Resource, fields
from app.services.auth_service import add_user, authenticate_user, get_all_users

# Define the namespace
auth_ns = Namespace('auth', description='Authentication related operations.')
Expand Down Expand Up @@ -43,3 +42,21 @@ def post(self):
return {'message': f'User {user.username} logged in successfully.', 'session_id': 'mock_session_id'}, 200
else:
return {'message': 'Username or password is incorrect.'}, 401

# Logout route
@auth_ns.route('/logout')
class UserLogout(Resource):
def post(self):
"""Log out a user."""
# For simplicity, returning a mock message. Implement actual session management.
return {'message': 'User logged out successfully.'}, 200

# Get all users route
@auth_ns.route('/users')
class AllUsers(Resource):
@auth_ns.marshal_list_with(registration_model)
def get(self):
"""Get all users."""
return get_all_users(), 200


10 changes: 10 additions & 0 deletions ShoppingApp/app/services/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ def authenticate_user(username, password):
if user and check_password_hash(user.password, password):
return user
return None

def logout_user(user_id):
user = next((u for u in users_db if u.id == user_id), None)
if user:
user.session_id = None
return {"message": "User logged out successfully"}
return {"message": "User not found"}

def get_all_users():
return users_db
9 changes: 5 additions & 4 deletions ShoppingApp/create_project_structure.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import os
import sys

def create_project_structure(base_path, structure):
def create_project_structure(structure, base_path=''):
for name, value in structure.items():
current_path = os.path.join(base_path, name)
if isinstance(value, dict): # It's a directory
os.makedirs(current_path, exist_ok=True)
create_project_structure(current_path, value) # Recurse into the directory
create_project_structure(value, current_path) # Recurse into the directory
else: # It's a file
with open(current_path, 'w') as f:
f.write(value) # Create an empty file for now
Expand Down Expand Up @@ -53,9 +54,9 @@ def create_project_structure(base_path, structure):
}

# Specify the base path where you want to create the project
base_path = ''
base_path = sys.argv[1] if len(sys.argv) > 1 else ''

# Create the project structure
create_project_structure(base_path, project_structure)
create_project_structure(project_structure, base_path)

print(f"Project structure has been created at {base_path}")
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Flask==2.0.1
flask-restx==0.5.1
Werkzeug==2.0.1
requests==2.26.0
File renamed without changes.
39 changes: 39 additions & 0 deletions ShoppingApp/tests/test_auth_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import unittest
from app import create_app

class TestAPI(unittest.TestCase):
def setUp(self):
self.app = create_app()

def test_user_registration_success(self):
with self.app.test_client() as client:
response = client.post('/auth/register', json={
'username': 'testuser',
'email': '[email protected]',
'password': 'testpassword'
})
self.assertEqual(response.status_code, 201)
self.assertEqual(response.json['message'], 'User registered successfully.')

def test_user_login_success(self):
# Register a user first
with self.app.test_client() as client:
response = client.post('/auth/register', json={
'username': 'testuser',
'email': '[email protected]',
'password': 'testpassword'
})
self.assertEqual(response.status_code, 201)

# Attempt to log in with the registered user credentials
with self.app.test_client() as client:
response = client.post('/auth/login', json={
'username': 'testuser',
'password': 'testpassword'
})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json['message'], 'User testuser logged in successfully.')
self.assertIsNotNone(response.json['session_id'])

if __name__ == '__main__':
unittest.main()
Empty file added ShoppingApp/utils/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from flask import jsonify, request, session
from app.models.product import Product

def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()

def generate_secure_id(user_identifier):
# Ensure user_identifier is a string. If it's not, you can convert it.
Expand Down
Loading