From f2ac7ed1fd3e764c5d99bdae922f5af3ffff00bb Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Dec 2024 14:19:33 -0500 Subject: [PATCH 01/26] testing --- .github/workflows/production.yml | 4 +- app/controllers/user_controller.py | 31 ++++++++ app/models/notification.py | 13 ++++ app/models/user.py | 23 ++++++ app/routers/user_routes.py | 33 -------- app/routes/user_routes.py | 14 ++++ app/static/js/profile.js | 13 ++++ app/templates/admin.html | 28 +++++++ app/templates/profile.html | 23 ++++++ tests/test_user_controller.py | 118 +++++++++++++++++++++++++++++ 10 files changed, 265 insertions(+), 35 deletions(-) create mode 100644 app/controllers/user_controller.py create mode 100644 app/models/notification.py create mode 100644 app/models/user.py create mode 100644 app/routes/user_routes.py create mode 100644 app/static/js/profile.js create mode 100644 app/templates/admin.html create mode 100644 app/templates/profile.html create mode 100644 tests/test_user_controller.py diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 566194f4a..8bfcad50a 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -70,9 +70,9 @@ jobs: uses: docker/build-push-action@v5 with: push: true - tags: woffee/wis_club_api:${{ github.sha }} # Uses the Git SHA for tagging, change 'woffee' to your DockerHub username + tags: wg99/wis_club_api:${{ github.sha }} # Uses the Git SHA for tagging, change 'woffee' to your DockerHub username platforms: linux/amd64,linux/arm64 # Multi-platform support - cache-from: type=registry,ref=woffee/wis_club_api:cache # change 'woffee' to your DockerHub username + cache-from: type=registry,ref=wg99/wis_club_api:cache # change 'woffee' to your DockerHub username cache-to: type=inline,mode=max # - name: Scan the Docker image diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py new file mode 100644 index 000000000..488e8876e --- /dev/null +++ b/app/controllers/user_controller.py @@ -0,0 +1,31 @@ +from flask import request, jsonify +from app.models.user import User +from app.models.notification import Notification + +def validate_profile_data(data): + if not data.get('name') or not data.get('bio') or not data.get('location'): + return False, "All fields (name, bio, location) are required." + return True, "" + +def update_profile(request): + try: + user_id = request.user.id + updates = request.json + is_valid, message = validate_profile_data(updates) + if not is_valid: + return jsonify({'error': message}), 400 + user = User.query.filter_by(id=user_id).update(updates) + user.save() + return jsonify(user.serialize()), 200 + except Exception as e: + return jsonify({'error': str(e)}), 400 + +def upgrade_to_professional(user_id): + try: + user = User.query.filter_by(id=user_id).update({'professional_status': True}) + user.save() + notification = Notification(user_id=user_id, message='Your account has been upgraded to professional status.') + notification.save() + return jsonify(user.serialize()), 200 + except Exception as e: + return jsonify({'error': str(e)}), 400 diff --git a/app/models/notification.py b/app/models/notification.py new file mode 100644 index 000000000..d6db8293e --- /dev/null +++ b/app/models/notification.py @@ -0,0 +1,13 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + +class Notification(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + message = db.Column(db.String(200)) + created_at = db.Column(db.DateTime, default=db.func.current_timestamp()) + + def save(self): + db.session.add(self) + db.session.commit() diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 000000000..9e340b57f --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,23 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50)) + bio = db.Column(db.String(200)) + location = db.Column(db.String(100)) + professional_status = db.Column(db.Boolean, default=False) + + def save(self): + db.session.add(self) + db.session.commit() + + def serialize(self): + return { + 'id': self.id, + 'name': self.name, + 'bio': self.bio, + 'location': self.location, + 'professional_status': self.professional_status + } diff --git a/app/routers/user_routes.py b/app/routers/user_routes.py index fd259258e..5c0c528ba 100644 --- a/app/routers/user_routes.py +++ b/app/routers/user_routes.py @@ -1,22 +1,3 @@ -""" -This Python file is part of a FastAPI application, demonstrating user management functionalities including creating, reading, -updating, and deleting (CRUD) user information. It uses OAuth2 with Password Flow for security, ensuring that only authenticated -users can perform certain operations. Additionally, the file showcases the integration of FastAPI with SQLAlchemy for asynchronous -database operations, enhancing performance by non-blocking database calls. - -The implementation emphasizes RESTful API principles, with endpoints for each CRUD operation and the use of HTTP status codes -and exceptions to communicate the outcome of operations. It introduces the concept of HATEOAS (Hypermedia as the Engine of -Application State) by including navigational links in API responses, allowing clients to discover other related operations dynamically. - -OAuth2PasswordBearer is employed to extract the token from the Authorization header and verify the user's identity, providing a layer -of security to the operations that manipulate user data. - -Key Highlights: -- Use of FastAPI's Dependency Injection system to manage database sessions and user authentication. -- Demonstrates how to perform CRUD operations in an asynchronous manner using SQLAlchemy with FastAPI. -- Implements HATEOAS by generating dynamic links for user-related actions, enhancing API discoverability. -- Utilizes OAuth2PasswordBearer for securing API endpoints, requiring valid access tokens for operations. -""" from builtins import dict, int, len, str from datetime import timedelta @@ -125,21 +106,7 @@ async def delete_user(user_id: UUID, db: AsyncSession = Depends(get_db), token: @router.post("/users/", response_model=UserResponse, status_code=status.HTTP_201_CREATED, tags=["User Management Requires (Admin or Manager Roles)"], name="create_user") async def create_user(user: UserCreate, request: Request, db: AsyncSession = Depends(get_db), email_service: EmailService = Depends(get_email_service), token: str = Depends(oauth2_scheme), current_user: dict = Depends(require_role(["ADMIN", "MANAGER"]))): - """ - Create a new user. - - This endpoint creates a new user with the provided information. If the email - already exists, it returns a 400 error. On successful creation, it returns the - newly created user's information along with links to related actions. - Parameters: - - user (UserCreate): The user information to create. - - request (Request): The request object. - - db (AsyncSession): The database session. - - Returns: - - UserResponse: The newly created user's information along with navigation links. - """ existing_user = await UserService.get_by_email(db, user.email) if existing_user: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already exists") diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py new file mode 100644 index 000000000..377f9baf8 --- /dev/null +++ b/app/routes/user_routes.py @@ -0,0 +1,14 @@ +from flask import Blueprint, request, jsonify +from app.controllers.user_controller import update_profile, upgrade_to_professional + +user_routes = Blueprint('user_routes', __name__) + +# Route to update user profile fields +@user_routes.route('/api/users/profile', methods=['PUT']) +def update_user_profile(): + return update_profile(request) + +# Route for managers and admins to upgrade user to professional status +@user_routes.route('/api/users//upgrade', methods=['POST']) +def upgrade_user(user_id): + return upgrade_to_professional(user_id) diff --git a/app/static/js/profile.js b/app/static/js/profile.js new file mode 100644 index 000000000..27cc7e888 --- /dev/null +++ b/app/static/js/profile.js @@ -0,0 +1,13 @@ +document.addEventListener('DOMContentLoaded', () => { + const form = document.querySelector('form'); + form.addEventListener('submit', (event) => { + const name = document.getElementById('name').value; + const bio = document.getElementById('bio').value; + const location = document.getElementById('location').value; + + if (!name || !bio || !location) { + event.preventDefault(); + alert('All fields are required.'); + } + }); +}); diff --git a/app/templates/admin.html b/app/templates/admin.html new file mode 100644 index 000000000..2e6e44e02 --- /dev/null +++ b/app/templates/admin.html @@ -0,0 +1,28 @@ + + + + + + + +
+

Admin Panel

+
+ + + +
+
+ {% for user in users %} +
+

{{ user.name }} ({{ user.email }})

+
+ +
+
+ {% endfor %} +
+
+ + + diff --git a/app/templates/profile.html b/app/templates/profile.html new file mode 100644 index 000000000..71ba5ba18 --- /dev/null +++ b/app/templates/profile.html @@ -0,0 +1,23 @@ + + + + + + + +
+

Profile

+
+ + + + + + + +
+

Professional Status: {{ 'Yes' if user.professional_status else 'No' }}

+
+ + + diff --git a/tests/test_user_controller.py b/tests/test_user_controller.py new file mode 100644 index 000000000..9e34ff7dc --- /dev/null +++ b/tests/test_user_controller.py @@ -0,0 +1,118 @@ +import pytest +from app import create_app, db +from app.models.user import User +from app.models.notification import Notification + +@pytest.fixture(scope='module') +def test_client(): + flask_app = create_app('testing') + testing_client = flask_app.test_client() + + ctx = flask_app.app_context() + ctx.push() + + yield testing_client + + ctx.pop() + +@pytest.fixture(scope='module') +def init_database(): + db.create_all() + + yield db + + db.session.remove() + db.drop_all() + +def test_update_user_profile(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.put('/api/users/profile', json={ + 'name': 'Jane Doe', + 'bio': 'Designer', + 'location': 'LA' + }, headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) + assert response.status_code == 200 + data = response.get_json() + assert data['name'] == 'Jane Doe' + assert data['bio'] == 'Designer' + assert data['location'] == 'LA' + +def test_update_user_profile_validation(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.put('/api/users/profile', json={ + 'name': '', + 'bio': 'Designer', + 'location': 'LA' + }, headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) + assert response.status_code == 400 + data = response.get_json() + assert 'error' in data + +def test_update_user_profile_missing_fields(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.put('/api/users/profile', json={ + 'name': 'Jane Doe', + 'bio': '', + 'location': 'LA' + }, headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) + assert response.status_code == 400 + data = response.get_json() + assert 'error' in data + +def test_update_user_profile_invalid_token(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.put('/api/users/profile', json={ + 'name': 'Jane Doe', + 'bio': 'Designer', + 'location': 'LA' + }, headers={'Authorization': 'Bearer invalid_token'}) + assert response.status_code == 401 + +def test_upgrade_user_to_professional(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': f'Bearer {admin_token}'}) + assert response.status_code == 200 + data = response.get_json() + assert data['professional_status'] is True + notification = Notification.query.filter_by(user_id=user.id).first() + assert notification is not None + assert notification.message == 'Your account has been upgraded to professional status.' + +def test_upgrade_user_to_professional_invalid_token(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) + assert response.status_code == 401 + +def test_upgrade_user_to_professional_non_admin(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + non_admin_user = User(name='Jane Doe', bio='Designer', location='LA') + non_admin_user.save() + response = test_client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': f'Bearer {non_admin_user.generate_auth_token()}'}) + assert response.status_code == 403 + +def test_get_user_profile(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.get(f'/api/users/{user.id}', headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) + assert response.status_code == 200 + data = response.get_json() + assert data['name'] == 'John Doe' + assert data['bio'] == 'Developer' + assert data['location'] == 'NYC' + +def test_get_user_profile_invalid_token(test_client, init_database): + user = User(name='John Doe', bio='Developer', location='NYC') + user.save() + response = test_client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) + assert response.status_code == 401 + +def test_get_user_profile_non_existent_user(test_client, init_database): + response = test_client.get('/api/users/999', headers={'Authorization': f'Bearer {admin_token}'}) + assert response.status_code == 404 From d1e3e424afe1ae245f69a500a53ae399a108aacb Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:34:57 -0500 Subject: [PATCH 02/26] Update production.yml --- .github/workflows/production.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 8bfcad50a..e6ba7ff76 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -3,10 +3,10 @@ name: CI/CD Pipeline on: push: branches: - - main + - changes pull_request: branches: - - main + - changes jobs: test: From 315ac0c2c737eae4051d472770073d9ce6ed6799 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:37:51 -0500 Subject: [PATCH 03/26] Update requirements.txt --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 297b7c04a..702aa3810 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,4 +60,6 @@ typing_extensions==4.10.0 uvicorn==0.29.0 validators==0.24.0 markdown2 -pyjwt \ No newline at end of file +pyjwt +Flask +Flask-SQLAlchemy From c34468bc618a3b05573532e8db3774f49592846b Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:46:42 -0500 Subject: [PATCH 04/26] Update __init__.py --- app/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index e69de29bb..ba448fe2d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -0,0 +1,16 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + +def create_app(config_name): + app = Flask(__name__) + app.config.from_object(config_name) + + db.init_app(app) + + # Import and register blueprints + from app.routes.user_routes import user_routes + app.register_blueprint(user_routes) + + return app From 7fa4e7bf2d6c2673f826a158ebb8aecb1b4a27ae Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:47:56 -0500 Subject: [PATCH 05/26] Update test_user_controller.py --- tests/test_user_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_user_controller.py b/tests/test_user_controller.py index 9e34ff7dc..8b47118ff 100644 --- a/tests/test_user_controller.py +++ b/tests/test_user_controller.py @@ -5,7 +5,7 @@ @pytest.fixture(scope='module') def test_client(): - flask_app = create_app('testing') + flask_app = create_app('config.TestingConfig') testing_client = flask_app.test_client() ctx = flask_app.app_context() From e740bf23c2feca09206448f4b000e85ecb6812c6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Dec 2024 14:56:32 -0500 Subject: [PATCH 06/26] added another file --- config.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 config.py diff --git a/config.py b/config.py new file mode 100644 index 000000000..7b8056e7e --- /dev/null +++ b/config.py @@ -0,0 +1,11 @@ +import os + +class Config: + SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess' + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///app.db' + SQLALCHEMY_TRACK_MODIFICATIONS = False + +class TestingConfig(Config): + TESTING = True + SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db' + WTF_CSRF_ENABLED = False From 2809f14ba91aa623352a16da0bbe3616078572b2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Dec 2024 15:21:54 -0500 Subject: [PATCH 07/26] added some changes --- tests/test_user_controller.py | 118 ------------------ tests/user_controller/test_user_controller.py | 106 ++++++++++++++++ 2 files changed, 106 insertions(+), 118 deletions(-) delete mode 100644 tests/test_user_controller.py create mode 100644 tests/user_controller/test_user_controller.py diff --git a/tests/test_user_controller.py b/tests/test_user_controller.py deleted file mode 100644 index 8b47118ff..000000000 --- a/tests/test_user_controller.py +++ /dev/null @@ -1,118 +0,0 @@ -import pytest -from app import create_app, db -from app.models.user import User -from app.models.notification import Notification - -@pytest.fixture(scope='module') -def test_client(): - flask_app = create_app('config.TestingConfig') - testing_client = flask_app.test_client() - - ctx = flask_app.app_context() - ctx.push() - - yield testing_client - - ctx.pop() - -@pytest.fixture(scope='module') -def init_database(): - db.create_all() - - yield db - - db.session.remove() - db.drop_all() - -def test_update_user_profile(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.put('/api/users/profile', json={ - 'name': 'Jane Doe', - 'bio': 'Designer', - 'location': 'LA' - }, headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) - assert response.status_code == 200 - data = response.get_json() - assert data['name'] == 'Jane Doe' - assert data['bio'] == 'Designer' - assert data['location'] == 'LA' - -def test_update_user_profile_validation(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.put('/api/users/profile', json={ - 'name': '', - 'bio': 'Designer', - 'location': 'LA' - }, headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) - assert response.status_code == 400 - data = response.get_json() - assert 'error' in data - -def test_update_user_profile_missing_fields(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.put('/api/users/profile', json={ - 'name': 'Jane Doe', - 'bio': '', - 'location': 'LA' - }, headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) - assert response.status_code == 400 - data = response.get_json() - assert 'error' in data - -def test_update_user_profile_invalid_token(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.put('/api/users/profile', json={ - 'name': 'Jane Doe', - 'bio': 'Designer', - 'location': 'LA' - }, headers={'Authorization': 'Bearer invalid_token'}) - assert response.status_code == 401 - -def test_upgrade_user_to_professional(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': f'Bearer {admin_token}'}) - assert response.status_code == 200 - data = response.get_json() - assert data['professional_status'] is True - notification = Notification.query.filter_by(user_id=user.id).first() - assert notification is not None - assert notification.message == 'Your account has been upgraded to professional status.' - -def test_upgrade_user_to_professional_invalid_token(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) - assert response.status_code == 401 - -def test_upgrade_user_to_professional_non_admin(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - non_admin_user = User(name='Jane Doe', bio='Designer', location='LA') - non_admin_user.save() - response = test_client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': f'Bearer {non_admin_user.generate_auth_token()}'}) - assert response.status_code == 403 - -def test_get_user_profile(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.get(f'/api/users/{user.id}', headers={'Authorization': f'Bearer {user.generate_auth_token()}'}) - assert response.status_code == 200 - data = response.get_json() - assert data['name'] == 'John Doe' - assert data['bio'] == 'Developer' - assert data['location'] == 'NYC' - -def test_get_user_profile_invalid_token(test_client, init_database): - user = User(name='John Doe', bio='Developer', location='NYC') - user.save() - response = test_client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) - assert response.status_code == 401 - -def test_get_user_profile_non_existent_user(test_client, init_database): - response = test_client.get('/api/users/999', headers={'Authorization': f'Bearer {admin_token}'}) - assert response.status_code == 404 diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py new file mode 100644 index 000000000..d934843c8 --- /dev/null +++ b/tests/user_controller/test_user_controller.py @@ -0,0 +1,106 @@ +import pytest +from app import create_app, db +from app.models.user import User +from flask import json + +@pytest.fixture +def client(): + app = create_app() + app.config['TESTING'] = True + with app.test_client() as client: + with app.app_context(): + db.create_all() + yield client + db.drop_all() + +def test_update_user_profile(client): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.put('/api/users/profile', json={ + 'name': 'Updated Name', + 'bio': 'Updated Bio', + 'location': 'Updated Location' + }) + assert response.status_code == 200 + data = json.loads(response.data) + assert data['name'] == 'Updated Name' + assert data['bio'] == 'Updated Bio' + assert data['location'] == 'Updated Location' + +def test_update_user_profile_validation(client): + response = client.put('/api/users/profile', json={ + 'name': '', + 'bio': 'Updated Bio', + 'location': 'Updated Location' + }) + assert response.status_code == 400 + data = json.loads(response.data) + assert 'error' in data + +def test_update_user_profile_missing_fields(client): + response = client.put('/api/users/profile', json={ + 'name': 'Updated Name', + 'bio': '', + 'location': 'Updated Location' + }) + assert response.status_code == 400 + data = json.loads(response.data) + assert 'error' in data + +def test_update_user_profile_invalid_token(client): + response = client.put('/api/users/profile', headers={'Authorization': 'Bearer invalid_token'}, json={ + 'name': 'Updated Name', + 'bio': 'Updated Bio', + 'location': 'Updated Location' + }) + assert response.status_code == 401 + data = json.loads(response.data) + assert 'error' in data + +def test_upgrade_user_to_professional(client): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.post(f'/api/users/{user.id}/upgrade') + assert response.status_code == 200 + data = json.loads(response.data) + assert data['professional_status'] == True + +def test_upgrade_user_to_professional_invalid_token(client): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) + assert response.status_code == 401 + data = json.loads(response.data) + assert 'error' in data + +def test_upgrade_user_to_professional_non_admin(client): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer non_admin_token'}) + assert response.status_code == 403 + data = json.loads(response.data) + assert 'error' in data + +def test_get_user_profile(client): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.get(f'/api/users/{user.id}') + assert response.status_code == 200 + data = json.loads(response.data) + assert data['name'] == 'Test User' + assert data['bio'] == 'Test Bio' + assert data['location'] == 'Test Location' + +def test_get_user_profile_invalid_token(client): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) + assert response.status_code == 401 + data = json.loads(response.data) + assert 'error' in data + +def test_get_user_profile_non_existent_user(client): + response = client.get('/api/users/999') + assert response.status_code == 404 + data = json.loads(response.data) + assert 'error' in data From 87a43dfe71b7ef2209f1cafbaa3b41e88b72b110 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:29:31 -0500 Subject: [PATCH 08/26] trying to fix --- app/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index ba448fe2d..b4ec90264 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,14 +3,17 @@ db = SQLAlchemy() -def create_app(config_name): +def create_app(config_name='default'): app = Flask(__name__) - app.config.from_object(config_name) + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///your_database.db' + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db.init_app(app) - # Import and register blueprints - from app.routes.user_routes import user_routes - app.register_blueprint(user_routes) + with app.app_context(): + from .routes.user_routes import user_routes + app.register_blueprint(user_routes) + + db.create_all() return app From b11c3a6b62272a5217e10b9f24b519f00e8d969d Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:30:18 -0500 Subject: [PATCH 09/26] Update test_user_controller.py --- tests/user_controller/test_user_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index d934843c8..55c05718c 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -5,7 +5,7 @@ @pytest.fixture def client(): - app = create_app() + app = create_app('testing') app.config['TESTING'] = True with app.test_client() as client: with app.app_context(): @@ -104,3 +104,4 @@ def test_get_user_profile_non_existent_user(client): assert response.status_code == 404 data = json.loads(response.data) assert 'error' in data + From 050d5b3f19181cb5716bb2e7c5562d4c0c799792 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Dec 2024 15:38:54 -0500 Subject: [PATCH 10/26] trying to fix --- app/controllers/user_controller.py | 18 +- app/models/notification.py | 4 +- app/models/user.py | 4 +- app/templates/profile.html | 1 + tests/user_controller/test_user_controller.py | 158 ++++++++++-------- 5 files changed, 101 insertions(+), 84 deletions(-) diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index 488e8876e..94c387987 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -1,6 +1,7 @@ from flask import request, jsonify from app.models.user import User from app.models.notification import Notification +from app import db def validate_profile_data(data): if not data.get('name') or not data.get('bio') or not data.get('location'): @@ -14,18 +15,27 @@ def update_profile(request): is_valid, message = validate_profile_data(updates) if not is_valid: return jsonify({'error': message}), 400 - user = User.query.filter_by(id=user_id).update(updates) - user.save() + user = User.query.filter_by(id=user_id).first() + if not user: + return jsonify({'error': 'User not found'}), 404 + for key, value in updates.items(): + setattr(user, key, value) + db.session.commit() return jsonify(user.serialize()), 200 except Exception as e: + db.session.rollback() return jsonify({'error': str(e)}), 400 def upgrade_to_professional(user_id): try: - user = User.query.filter_by(id=user_id).update({'professional_status': True}) - user.save() + user = User.query.filter_by(id=user_id).first() + if not user: + return jsonify({'error': 'User not found'}), 404 + user.professional_status = True + db.session.commit() notification = Notification(user_id=user_id, message='Your account has been upgraded to professional status.') notification.save() return jsonify(user.serialize()), 200 except Exception as e: + db.session.rollback() return jsonify({'error': str(e)}), 400 diff --git a/app/models/notification.py b/app/models/notification.py index d6db8293e..d212bd490 100644 --- a/app/models/notification.py +++ b/app/models/notification.py @@ -1,6 +1,4 @@ -from flask_sqlalchemy import SQLAlchemy - -db = SQLAlchemy() +from app import db class Notification(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/app/models/user.py b/app/models/user.py index 9e340b57f..3a1fae04e 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,6 +1,4 @@ -from flask_sqlalchemy import SQLAlchemy - -db = SQLAlchemy() +from app import db class User(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/app/templates/profile.html b/app/templates/profile.html index 71ba5ba18..c4fa23694 100644 --- a/app/templates/profile.html +++ b/app/templates/profile.html @@ -8,6 +8,7 @@

Profile

+ diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index d934843c8..2d50116ec 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -5,102 +5,112 @@ @pytest.fixture def client(): - app = create_app() + app = create_app('testing') app.config['TESTING'] = True with app.test_client() as client: with app.app_context(): db.create_all() yield client - db.drop_all() + db.drop_all() def test_update_user_profile(client): - user = User(name="Test User", bio="Test Bio", location="Test Location") - user.save() - response = client.put('/api/users/profile', json={ - 'name': 'Updated Name', - 'bio': 'Updated Bio', - 'location': 'Updated Location' - }) - assert response.status_code == 200 - data = json.loads(response.data) - assert data['name'] == 'Updated Name' - assert data['bio'] == 'Updated Bio' - assert data['location'] == 'Updated Location' + with client.application.app_context(): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.put('/api/users/profile', json={ + 'name': 'Updated Name', + 'bio': 'Updated Bio', + 'location': 'Updated Location' + }) + assert response.status_code == 200 + data = json.loads(response.data) + assert data['name'] == 'Updated Name' + assert data['bio'] == 'Updated Bio' + assert data['location'] == 'Updated Location' def test_update_user_profile_validation(client): - response = client.put('/api/users/profile', json={ - 'name': '', - 'bio': 'Updated Bio', - 'location': 'Updated Location' - }) - assert response.status_code == 400 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + response = client.put('/api/users/profile', json={ + 'name': '', + 'bio': 'Updated Bio', + 'location': 'Updated Location' + }) + assert response.status_code == 400 + data = json.loads(response.data) + assert 'error' in data def test_update_user_profile_missing_fields(client): - response = client.put('/api/users/profile', json={ - 'name': 'Updated Name', - 'bio': '', - 'location': 'Updated Location' - }) - assert response.status_code == 400 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + response = client.put('/api/users/profile', json={ + 'name': 'Updated Name', + 'bio': '', + 'location': 'Updated Location' + }) + assert response.status_code == 400 + data = json.loads(response.data) + assert 'error' in data def test_update_user_profile_invalid_token(client): - response = client.put('/api/users/profile', headers={'Authorization': 'Bearer invalid_token'}, json={ - 'name': 'Updated Name', - 'bio': 'Updated Bio', - 'location': 'Updated Location' - }) - assert response.status_code == 401 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + response = client.put('/api/users/profile', headers={'Authorization': 'Bearer invalid_token'}, json={ + 'name': 'Updated Name', + 'bio': 'Updated Bio', + 'location': 'Updated Location' + }) + assert response.status_code == 401 + data = json.loads(response.data) + assert 'error' in data def test_upgrade_user_to_professional(client): - user = User(name="Test User", bio="Test Bio", location="Test Location") - user.save() - response = client.post(f'/api/users/{user.id}/upgrade') - assert response.status_code == 200 - data = json.loads(response.data) - assert data['professional_status'] == True + with client.application.app_context(): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.post(f'/api/users/{user.id}/upgrade') + assert response.status_code == 200 + data = json.loads(response.data) + assert data['professional_status'] == True def test_upgrade_user_to_professional_invalid_token(client): - user = User(name="Test User", bio="Test Bio", location="Test Location") - user.save() - response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) - assert response.status_code == 401 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) + assert response.status_code == 401 + data = json.loads(response.data) + assert 'error' in data def test_upgrade_user_to_professional_non_admin(client): - user = User(name="Test User", bio="Test Bio", location="Test Location") - user.save() - response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer non_admin_token'}) - assert response.status_code == 403 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer non_admin_token'}) + assert response.status_code == 403 + data = json.loads(response.data) + assert 'error' in data def test_get_user_profile(client): - user = User(name="Test User", bio="Test Bio", location="Test Location") - user.save() - response = client.get(f'/api/users/{user.id}') - assert response.status_code == 200 - data = json.loads(response.data) - assert data['name'] == 'Test User' - assert data['bio'] == 'Test Bio' - assert data['location'] == 'Test Location' + with client.application.app_context(): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.get(f'/api/users/{user.id}') + assert response.status_code == 200 + data = json.loads(response.data) + assert data['name'] == 'Test User' + assert data['bio'] == 'Test Bio' + assert data['location'] == 'Test Location' def test_get_user_profile_invalid_token(client): - user = User(name="Test User", bio="Test Bio", location="Test Location") - user.save() - response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) - assert response.status_code == 401 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + user = User(name="Test User", bio="Test Bio", location="Test Location") + user.save() + response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) + assert response.status_code == 401 + data = json.loads(response.data) + assert 'error' in data def test_get_user_profile_non_existent_user(client): - response = client.get('/api/users/999') - assert response.status_code == 404 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + response = client.get('/api/users/999') + assert response.status_code == 404 + data = json.loads(response.data) + assert 'error' in data From 0257739ec4c267c132efd414177ec868d1b66638 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:02:01 -0500 Subject: [PATCH 11/26] Update user_controller.py --- app/controllers/user_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index 94c387987..6869a451a 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -1,4 +1,4 @@ -from flask import request, jsonify +from flask import request, jsonify, g from app.models.user import User from app.models.notification import Notification from app import db @@ -10,7 +10,7 @@ def validate_profile_data(data): def update_profile(request): try: - user_id = request.user.id + user_id = g.user.id updates = request.json is_valid, message = validate_profile_data(updates) if not is_valid: @@ -39,3 +39,4 @@ def upgrade_to_professional(user_id): except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 400 + From e49cc24866ced5eb9f981e015b4ceb884bad76c7 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:03:08 -0500 Subject: [PATCH 12/26] Update user_routes.py --- app/routes/user_routes.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index 377f9baf8..d982f1a04 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -1,14 +1,32 @@ -from flask import Blueprint, request, jsonify +from flask import Blueprint, request, jsonify, g from app.controllers.user_controller import update_profile, upgrade_to_professional +from app.models.user import User user_routes = Blueprint('user_routes', __name__) +@user_routes.before_request +def load_user(): + token = request.headers.get('Authorization') + if token: + token = token.replace('Bearer ', '') + user = User.query.filter_by(token=token).first() + if user: + g.user = user + else: + return jsonify({'error': 'Invalid token'}), 401 + else: + g.user = None + # Route to update user profile fields @user_routes.route('/api/users/profile', methods=['PUT']) def update_user_profile(): + if not g.user: + return jsonify({'error': 'Unauthorized'}), 401 return update_profile(request) # Route for managers and admins to upgrade user to professional status @user_routes.route('/api/users//upgrade', methods=['POST']) def upgrade_user(user_id): + if not g.user: + return jsonify({'error': 'Unauthorized'}), 401 return upgrade_to_professional(user_id) From 4aacf58cc44f8873fe5cf3811174ca5bcc9c26ba Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:03:47 -0500 Subject: [PATCH 13/26] Update test_user_controller.py --- tests/user_controller/test_user_controller.py | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index 1de488868..dd22f1798 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -1,3 +1,4 @@ + import pytest from app import create_app, db from app.models.user import User @@ -15,9 +16,9 @@ def client(): def test_update_user_profile(client): with client.application.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() - response = client.put('/api/users/profile', json={ + response = client.put('/api/users/profile', headers={'Authorization': 'Bearer valid_token'}, json={ 'name': 'Updated Name', 'bio': 'Updated Bio', 'location': 'Updated Location' @@ -30,7 +31,9 @@ def test_update_user_profile(client): def test_update_user_profile_validation(client): with client.application.app_context(): - response = client.put('/api/users/profile', json={ + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") + user.save() + response = client.put('/api/users/profile', headers={'Authorization': 'Bearer valid_token'}, json={ 'name': '', 'bio': 'Updated Bio', 'location': 'Updated Location' @@ -41,7 +44,9 @@ def test_update_user_profile_validation(client): def test_update_user_profile_missing_fields(client): with client.application.app_context(): - response = client.put('/api/users/profile', json={ + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") + user.save() + response = client.put('/api/users/profile', headers={'Authorization': 'Bearer valid_token'}, json={ 'name': 'Updated Name', 'bio': '', 'location': 'Updated Location' @@ -52,6 +57,8 @@ def test_update_user_profile_missing_fields(client): def test_update_user_profile_invalid_token(client): with client.application.app_context(): + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") + user.save() response = client.put('/api/users/profile', headers={'Authorization': 'Bearer invalid_token'}, json={ 'name': 'Updated Name', 'bio': 'Updated Bio', @@ -63,16 +70,16 @@ def test_update_user_profile_invalid_token(client): def test_upgrade_user_to_professional(client): with client.application.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() - response = client.post(f'/api/users/{user.id}/upgrade') + response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer valid_token'}) assert response.status_code == 200 data = json.loads(response.data) assert data['professional_status'] == True def test_upgrade_user_to_professional_invalid_token(client): with client.application.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) assert response.status_code == 401 @@ -81,7 +88,7 @@ def test_upgrade_user_to_professional_invalid_token(client): def test_upgrade_user_to_professional_non_admin(client): with client.application.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer non_admin_token'}) assert response.status_code == 403 @@ -90,9 +97,9 @@ def test_upgrade_user_to_professional_non_admin(client): def test_get_user_profile(client): with client.application.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() - response = client.get(f'/api/users/{user.id}') + response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer valid_token'}) assert response.status_code == 200 data = json.loads(response.data) assert data['name'] == 'Test User' @@ -101,7 +108,7 @@ def test_get_user_profile(client): def test_get_user_profile_invalid_token(client): with client.application.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) assert response.status_code == 401 @@ -109,7 +116,8 @@ def test_get_user_profile_invalid_token(client): assert 'error' in data def test_get_user_profile_non_existent_user(client): - response = client.get('/api/users/999') - assert response.status_code == 404 - data = json.loads(response.data) - assert 'error' in data + with client.application.app_context(): + response = client.get('/api/users/999', headers={'Authorization': 'Bearer valid_token'}) + assert response.status_code == 404 + data = json.loads(response.data) + assert 'error' in data From ab9bb0c5664ad88ffcb0f488d598f9e7001a69d8 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:08:55 -0500 Subject: [PATCH 14/26] adding changes --- app/models/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/user.py b/app/models/user.py index 3a1fae04e..1fd09071b 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -6,6 +6,7 @@ class User(db.Model): bio = db.Column(db.String(200)) location = db.Column(db.String(100)) professional_status = db.Column(db.Boolean, default=False) + token = db.Column(db.String(200), unique=True) def save(self): db.session.add(self) @@ -19,3 +20,4 @@ def serialize(self): 'location': self.location, 'professional_status': self.professional_status } + From 266c83d454b08aedc37c338c809ab5aeb34c32a3 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:09:55 -0500 Subject: [PATCH 15/26] Update test_user_controller.py --- tests/user_controller/test_user_controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index dd22f1798..aea49307a 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -1,4 +1,3 @@ - import pytest from app import create_app, db from app.models.user import User From 2b6367db59a48c154ac8355fb3d4c5617c9ea1c1 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:15:17 -0500 Subject: [PATCH 16/26] Update user_controller.py --- app/controllers/user_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index 6869a451a..21dea26ec 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -40,3 +40,4 @@ def upgrade_to_professional(user_id): db.session.rollback() return jsonify({'error': str(e)}), 400 + From 198bea869afb548e4437f4898b95cf3a95a801c6 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:15:56 -0500 Subject: [PATCH 17/26] Update user_routes.py --- app/routes/user_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index d982f1a04..a60b8b0b3 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -30,3 +30,4 @@ def upgrade_user(user_id): if not g.user: return jsonify({'error': 'Unauthorized'}), 401 return upgrade_to_professional(user_id) + From 240e4f4fa018f27017499e27ac9465842f1e89b4 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:24:19 -0500 Subject: [PATCH 18/26] Update user_routes.py --- app/routes/user_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index a60b8b0b3..b428c477f 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -25,7 +25,7 @@ def update_user_profile(): return update_profile(request) # Route for managers and admins to upgrade user to professional status -@user_routes.route('/api/users//upgrade', methods=['POST']) +@user_routes.route('/api/users//upgrade', methods['POST']) def upgrade_user(user_id): if not g.user: return jsonify({'error': 'Unauthorized'}), 401 From e84ee571c01a111e52f8fb4d9d56f17886506035 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:27:46 -0500 Subject: [PATCH 19/26] Update user_routes.py --- app/routes/user_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index b428c477f..a60b8b0b3 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -25,7 +25,7 @@ def update_user_profile(): return update_profile(request) # Route for managers and admins to upgrade user to professional status -@user_routes.route('/api/users//upgrade', methods['POST']) +@user_routes.route('/api/users//upgrade', methods=['POST']) def upgrade_user(user_id): if not g.user: return jsonify({'error': 'Unauthorized'}), 401 From db01a73a1ee9efd29a968c7c37e916b72b077251 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:33:28 -0500 Subject: [PATCH 20/26] Update test_user_controller.py --- tests/user_controller/test_user_controller.py | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index aea49307a..1376a6d02 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -4,17 +4,20 @@ from flask import json @pytest.fixture -def client(): +def app(): app = create_app('testing') app.config['TESTING'] = True - with app.test_client() as client: - with app.app_context(): - db.create_all() - yield client - db.drop_all() + with app.app_context(): + db.create_all() + yield app + db.drop_all() -def test_update_user_profile(client): - with client.application.app_context(): +@pytest.fixture +def client(app): + return app.test_client() + +def test_update_user_profile(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.put('/api/users/profile', headers={'Authorization': 'Bearer valid_token'}, json={ @@ -28,8 +31,8 @@ def test_update_user_profile(client): assert data['bio'] == 'Updated Bio' assert data['location'] == 'Updated Location' -def test_update_user_profile_validation(client): - with client.application.app_context(): +def test_update_user_profile_validation(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.put('/api/users/profile', headers={'Authorization': 'Bearer valid_token'}, json={ @@ -41,8 +44,8 @@ def test_update_user_profile_validation(client): data = json.loads(response.data) assert 'error' in data -def test_update_user_profile_missing_fields(client): - with client.application.app_context(): +def test_update_user_profile_missing_fields(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.put('/api/users/profile', headers={'Authorization': 'Bearer valid_token'}, json={ @@ -54,8 +57,8 @@ def test_update_user_profile_missing_fields(client): data = json.loads(response.data) assert 'error' in data -def test_update_user_profile_invalid_token(client): - with client.application.app_context(): +def test_update_user_profile_invalid_token(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.put('/api/users/profile', headers={'Authorization': 'Bearer invalid_token'}, json={ @@ -67,8 +70,8 @@ def test_update_user_profile_invalid_token(client): data = json.loads(response.data) assert 'error' in data -def test_upgrade_user_to_professional(client): - with client.application.app_context(): +def test_upgrade_user_to_professional(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer valid_token'}) @@ -76,8 +79,8 @@ def test_upgrade_user_to_professional(client): data = json.loads(response.data) assert data['professional_status'] == True -def test_upgrade_user_to_professional_invalid_token(client): - with client.application.app_context(): +def test_upgrade_user_to_professional_invalid_token(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) @@ -85,8 +88,8 @@ def test_upgrade_user_to_professional_invalid_token(client): data = json.loads(response.data) assert 'error' in data -def test_upgrade_user_to_professional_non_admin(client): - with client.application.app_context(): +def test_upgrade_user_to_professional_non_admin(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer non_admin_token'}) @@ -94,8 +97,8 @@ def test_upgrade_user_to_professional_non_admin(client): data = json.loads(response.data) assert 'error' in data -def test_get_user_profile(client): - with client.application.app_context(): +def test_get_user_profile(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer valid_token'}) @@ -105,8 +108,8 @@ def test_get_user_profile(client): assert data['bio'] == 'Test Bio' assert data['location'] == 'Test Location' -def test_get_user_profile_invalid_token(client): - with client.application.app_context(): +def test_get_user_profile_invalid_token(client, app): + with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) @@ -114,8 +117,8 @@ def test_get_user_profile_invalid_token(client): data = json.loads(response.data) assert 'error' in data -def test_get_user_profile_non_existent_user(client): - with client.application.app_context(): +def test_get_user_profile_non_existent_user(client, app): + with app.app_context(): response = client.get('/api/users/999', headers={'Authorization': 'Bearer valid_token'}) assert response.status_code == 404 data = json.loads(response.data) From 038da08dad384cdded6b32711d12e08194b02e16 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:41:14 -0500 Subject: [PATCH 21/26] Update user_controller.py --- app/controllers/user_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index 21dea26ec..8b1f57f77 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -28,6 +28,8 @@ def update_profile(request): def upgrade_to_professional(user_id): try: + if g.user.role not in ['admin', 'manager']: + return jsonify({'error': 'Forbidden'}), 403 user = User.query.filter_by(id=user_id).first() if not user: return jsonify({'error': 'User not found'}), 404 @@ -39,5 +41,3 @@ def upgrade_to_professional(user_id): except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 400 - - From 8d46a33d9ad188499284113c2a4d64be75face05 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:41:47 -0500 Subject: [PATCH 22/26] Update test_user_controller.py --- tests/user_controller/test_user_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index 1376a6d02..238b8ea0d 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -72,7 +72,7 @@ def test_update_user_profile_invalid_token(client, app): def test_upgrade_user_to_professional(client, app): with app.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token", role="admin") user.save() response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer valid_token'}) assert response.status_code == 200 @@ -81,7 +81,7 @@ def test_upgrade_user_to_professional(client, app): def test_upgrade_user_to_professional_invalid_token(client, app): with app.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token", role="admin") user.save() response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer invalid_token'}) assert response.status_code == 401 @@ -90,9 +90,9 @@ def test_upgrade_user_to_professional_invalid_token(client, app): def test_upgrade_user_to_professional_non_admin(client, app): with app.app_context(): - user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token", role="user") user.save() - response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer non_admin_token'}) + response = client.post(f'/api/users/{user.id}/upgrade', headers={'Authorization': 'Bearer valid_token'}) assert response.status_code == 403 data = json.loads(response.data) assert 'error' in data From 72b8cdee66d7cf67b7ab31568dde2767d9416d72 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:42:58 -0500 Subject: [PATCH 23/26] fixing --- app/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.py b/app/models/user.py index 1fd09071b..c9807f5d0 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -7,6 +7,7 @@ class User(db.Model): location = db.Column(db.String(100)) professional_status = db.Column(db.Boolean, default=False) token = db.Column(db.String(200), unique=True) + role = db.Column(db.String(50), default='user') def save(self): db.session.add(self) @@ -20,4 +21,3 @@ def serialize(self): 'location': self.location, 'professional_status': self.professional_status } - From ff01e55b0b60f6dfff362601434f0a13cac72fd2 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:47:35 -0500 Subject: [PATCH 24/26] fixing issue --- tests/user_controller/test_user_controller.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index 238b8ea0d..9f3db1e71 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -101,7 +101,8 @@ def test_get_user_profile(client, app): with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() - response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer valid_token'}) + user_id = user.id # Ensure the user ID is retrieved after saving + response = client.get(f'/api/users/{user_id}', headers={'Authorization': 'Bearer valid_token'}) assert response.status_code == 200 data = json.loads(response.data) assert data['name'] == 'Test User' @@ -112,7 +113,8 @@ def test_get_user_profile_invalid_token(client, app): with app.app_context(): user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") user.save() - response = client.get(f'/api/users/{user.id}', headers={'Authorization': 'Bearer invalid_token'}) + user_id = user.id # Ensure the user ID is retrieved after saving + response = client.get(f'/api/users/{user_id}', headers={'Authorization': 'Bearer invalid_token'}) assert response.status_code == 401 data = json.loads(response.data) assert 'error' in data @@ -123,3 +125,4 @@ def test_get_user_profile_non_existent_user(client, app): assert response.status_code == 404 data = json.loads(response.data) assert 'error' in data + From 4120b2ac40d840ed55c2ea77523daaa5c5eacb66 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:52:31 -0500 Subject: [PATCH 25/26] fixing --- app/routes/user_routes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index a60b8b0b3..ff394ad2e 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -31,3 +31,13 @@ def upgrade_user(user_id): return jsonify({'error': 'Unauthorized'}), 401 return upgrade_to_professional(user_id) +# Route to get user details by ID +@user_routes.route('/api/users/', methods=['GET']) +def get_user(user_id): + if not g.user: + return jsonify({'error': 'Unauthorized'}), 401 + user = User.query.filter_by(id=user_id).first() + if not user: + return jsonify({'error': 'User not found'}), 404 + return jsonify(user.serialize()), 200 + From 71c13a0928d0258aed29f6faa336c7a650909b56 Mon Sep 17 00:00:00 2001 From: wg99 <146656013+wg99nj@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:56:34 -0500 Subject: [PATCH 26/26] fixed --- tests/user_controller/test_user_controller.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/user_controller/test_user_controller.py b/tests/user_controller/test_user_controller.py index 9f3db1e71..9d0c8d2d9 100644 --- a/tests/user_controller/test_user_controller.py +++ b/tests/user_controller/test_user_controller.py @@ -121,6 +121,9 @@ def test_get_user_profile_invalid_token(client, app): def test_get_user_profile_non_existent_user(client, app): with app.app_context(): + # Ensure a valid token is used for authorization + user = User(name="Test User", bio="Test Bio", location="Test Location", token="valid_token") + user.save() response = client.get('/api/users/999', headers={'Authorization': 'Bearer valid_token'}) assert response.status_code == 404 data = json.loads(response.data)