diff --git a/README.md b/README.md index 7cec097..ce48949 100644 --- a/README.md +++ b/README.md @@ -251,8 +251,37 @@ We are going to make six figure bet on you. You are going to put your career in We deeply appreciate the time you are taking to ensure joining Victory is of benefit to all concerned (yourself, Victory and our clients). -# 01 Added this comment for testing purposes. -# 02 Post-pull of action workflow to sync local with remote. -# 03 Error in workflow from Poetry version. Changed from 1.5.1 to 1.8.3. -# 04 Pyproject.toml changed significantly since poetry.lock was last generated. -# 05 04 push did not activate action. +# API calls + +REGISTER +Invoke-WebRequest -Uri http://127.0.0.1:5000/register -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"username":"Dev Userson", "email":"dev.userson@example.com", "password":"sosecure"}' + +Invoke-WebRequest -Uri http://127.0.0.1:5000/register -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"username":"Scott Swain", "email":"scott@oceanmedia.net", "password":"sosecure"}' + +Invoke-WebRequest -Uri http://127.0.0.1:5000/register -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"username":"Bozo Clown", "email":"bozo@oceanmedia.net", "password":"sosecure"}' + +Invoke-WebRequest -Uri http://127.0.0.1:5000/register -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"username":"Bruce Lee", "email":"bruce@lee.net", "password":"sosecure"}' + +LOGIN +Invoke-WebRequest -Uri http://127.0.0.1:5000/login -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"email":"dev.userson@example.com", "password":"sosecure"}' + +TOGGLE ACTIVE +Invoke-WebRequest -Uri http://127.0.0.1:5000/toggle-active -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"email":"dev.userson@example.com"}' + +SHOW ALL USERS (deprecated to the next two calls) +Invoke-WebRequest -Uri http://127.0.0.1:5000/users -Method GET -Headers @{"Content-Type" = "application/json"} + +SHOW ALL USERS with ROLES +Invoke-WebRequest -Uri http://127.0.0.1:5000/users-roles -Method GET -Headers @{"Content-Type" = "application/json"} + +ACCESS REPORT +Invoke-WebRequest -Uri http://127.0.0.1:5000/access-report -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"limit_to":"all_users"}' + +DELETE USER +Invoke-WebRequest -Uri http://127.0.0.1:5000/delete-user -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"email":"scott@oceanmedia.net"}' + +CREATE ROLE(S) +Invoke-WebRequest -Uri http://127.0.0.1:5000/create-roles -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"roles_depts":["Senior Dev,Getting Started", "Dev,Getting Started"]}' + +ASSIGN ROLE(S) +Invoke-WebRequest -Uri http://127.0.0.1:5000/assign-roles -Method POST -Headers @{"Content-Type" = "application/json"} -Body '{"emails_roles_depts":["dev.userson@example.com,Senior Dev,Getting Started", "scott@oceanmedia.net,Dev,Getting Started"]}' diff --git a/app/models.py b/app/models.py index c794eaf..c002ab7 100644 --- a/app/models.py +++ b/app/models.py @@ -26,13 +26,13 @@ class UserActiveStatusChange(db.Model): user = db.relationship("User", backref="status_changes") -"""Allow for the creation of one or more roles with attributes +""" Allow for the creation of one or more roles with attributes role_id, role_name, and department_name. Combination of role_name and department_name is unique Allow for a user to be assigned one or more roles and for a role to be assigned to one or more users. Users_roles table relies on roles_lookup -for putting a name and dept to a role id.""" +for putting a name and dept to a role id. """ class User(db.Model): @@ -55,7 +55,7 @@ def __repr__(self): return f"" -class UserRole(db.Model): +class UsersRoles(db.Model): __tablename__ = "users_roles" id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index 094e2b3..c270087 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -1,5 +1,5 @@ from flask import Blueprint, Response, jsonify, request -from ..models import User, UserActiveStatusChange +from ..models import User, UserActiveStatusChange, RolesLookup, UsersRoles from ..extensions import db from ..services.user_service import create_user, check_password import logging @@ -21,6 +21,7 @@ # Route to register a new user. @user_bp.route("/register", methods=["POST"]) def register(): + # Add user authentication data = request.get_json() username = data.get("username") email = data.get("email") @@ -33,6 +34,7 @@ def register(): # Route to log in a user. @user_bp.route("/login", methods=["POST"]) def login(): + # Add user authentication data = request.get_json() email = data.get("email") password = data.get("password") @@ -52,6 +54,7 @@ def profile(): # Route to hit to toggle active/inactive status of a user. @user_bp.route("/toggle-active", methods=["POST"]) def toggle_active(): + # Add user authentication data = request.get_json() email = data.get("email") user = User.query.filter_by(email=email).first() @@ -71,6 +74,7 @@ def toggle_active(): # Deprecated in favor of access-report route. @user_bp.route("/users", methods=["GET"]) def users(): + # Add user authentication users = User.query.all() user_list = [] for user in users: @@ -92,6 +96,7 @@ def users(): # Route to show all users. @user_bp.route("/access-report", methods=["POST"]) def access_report(): + # Add user authentication data = request.get_json() limit_to = data.get("limit_to") # limit_to may be "all_users", "active_users", or "inactive_users" @@ -120,9 +125,44 @@ def access_report(): return Response(response, mimetype="application/json"), 200 +# Route to show all users and their roles. +@user_bp.route("/users-roles", methods=["GET"]) +def users_roles(): + # Add user authentication + users = User.query.all() + user_list = [] + for user in users: + id_user = user.id + # for each user, get the roles and departments using the users_roles table + roles_depts = ( + db.session.query(RolesLookup.role_name, RolesLookup.department_name) + .join(UsersRoles, RolesLookup.id == UsersRoles.role_id) + .filter(UsersRoles.user_id == id_user) + .all() + ) + roles_list = [] + for role_dept in roles_depts: + roles_list.append(f"{role_dept[0]}/{role_dept[1]}") + + user_list.append( + { + "user": user.username, + "email": user.email, + "roles": str(roles_list), + "active": user.active, + } + ) + logger.debug( + f"{user.username} | {user.email} | Active: {user.active} | Roles: {str(roles_list)}" + ) + response = json.dumps(user_list) + return Response(response, mimetype="application/json"), 200 + + # Route to delete a user. @user_bp.route("/delete-user", methods=["POST"]) def delete_user(): + # Add user authentication data = request.get_json() email = data.get("email") user = User.query.filter_by(email=email).first() @@ -138,3 +178,95 @@ def delete_user(): db.session.commit() logger.debug(f"{user} deleted") return jsonify({"message": "User deleted"}), 200 + + +""" Create role(s)/dept(s) in roles_lookup with attributes role_name and department_name. +Combination of role_name and department_name is unique. """ + + +@user_bp.route("/create-roles", methods=["POST"]) +def create_roles(): + # Add user authentication + """POST looks like: + Invoke-WebRequest -Uri http://127.0.0.1:5000/create-roles -Method POST -Headers @{"Content-Type" = "application/json"} + -Body '{"role_dept":"dev,accounting", "role_dept":"admin,logistics"}'""" + data = request.get_json() + # The following line is only getting the first role_dept, not all of them. + roles_depts = data.get("roles_depts") # Expecting a list of roles and departments + logger.debug(f"roles_depts={str(roles_depts)}") + # Add input validation here. + # Add error checking. + for role_dept in roles_depts: + # logger.debug(f"role_dept={str(role_dept)}") + # Chose not to use tuple unpacking below, for clarity/debugging/scalability. + role_name = role_dept.split(",")[0] + dept_name = role_dept.split(",")[1] + + # Check if the role/dept combos exist and if not, add to roles_lookup + role_exists = RolesLookup.query.filter_by( + role_name=role_name, department_name=dept_name + ).first() + if role_exists is None: + logger.debug(f"Role/dept {role_name}/{dept_name} combination not found") + new_role = RolesLookup(role_name=role_name, department_name=dept_name) + logger.debug(f"Role/dept {role_name}/{dept_name} combination added") + db.session.add(new_role) + db.session.commit() + + return jsonify({"message": "Roles created"}), 201 + + +""" Allow for a user to be assigned one or more roles. + This will be done by adding record(s) to the users_roles table. + Potential for this to receive a list of roles to assign to a user + or a list of users (via email) to assign role(s) to. """ + + +@user_bp.route("/assign-roles", methods=["POST"]) +def assign_roles(): + """POST looks like: + Invoke-WebRequest -Uri http://127.0.0.1:5000/assign-roles -Method POST -Headers @{"Content-Type" = "application/json"} + -Body '{"emails_roles_depts":["bozo@oceanmedia.net,dev,accounting", "scotter@oceanmedia.net,admin,logistics"]}' + """ + + data = request.get_json() + emails_roles_depts = data.get( + "emails_roles_depts" + ) # Expecting a list of roles and departments + + if not emails_roles_depts: + return jsonify({"message": "Invalid input"}), 400 + + for email_role_dept in emails_roles_depts: + parts = email_role_dept.split(",") + if len(parts) != 3: + logger.debug(f"Invalid entry: {email_role_dept}") + continue + + user_email, role_name, dept_name = parts + + # Check if the role/dept combo exists and if not, add to roles_lookup + role_exists = RolesLookup.query.filter_by( + role_name=role_name, department_name=dept_name + ).first() + if role_exists is None: + new_role = RolesLookup(role_name=role_name, department_name=dept_name) + db.session.add(new_role) + db.session.commit() + role_exists = new_role + + # Assign the role to the user + user = User.query.filter_by(email=user_email).first() + if user is None: + logger.debug(f"User with email {user_email} not found") + continue + + user_role_exists = UsersRoles.query.filter_by( + user_id=user.id, role_id=role_exists.id + ).first() + if user_role_exists is None: + user_role = UsersRoles(user_id=user.id, role_id=role_exists.id) + db.session.add(user_role) + + db.session.commit() + return jsonify({"message": "Roles assigned"}), 200