Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
735a42f
Refactor admin controller
andreaselenaa Nov 21, 2025
870cbfd
Refactor auth controller
andreaselenaa Nov 21, 2025
190712d
Merge branch 'main' of https://github.com/kaitlynkhan/AgileMindsProject
andreaselenaa Nov 21, 2025
445c3c9
Refactored auth and admin controllers
andreaselenaa Nov 21, 2025
01cc6ec
Refactor staff controller with helpers
andreaselenaa Nov 21, 2025
c19973d
Refactored user controllers
andreaselenaa Nov 21, 2025
cf2ef55
Refactor admin controller & schedule with strategy pattern
andreaselenaa Nov 22, 2025
1a6f8a6
Update user.py
tlbuwi01 Nov 22, 2025
8c97557
Update user.py
tlbuwi01 Nov 22, 2025
0636636
Update staff.py
tlbuwi01 Nov 22, 2025
81879c0
Update staff.py
tlbuwi01 Nov 22, 2025
d11a1a2
Update staff.py
tlbuwi01 Nov 22, 2025
ada145c
Update user.py
tlbuwi01 Nov 22, 2025
92cd822
Update staff.py
tlbuwi01 Nov 22, 2025
c64d67c
Update admin.py
tlbuwi01 Nov 22, 2025
d3ebb2d
Fix imports and controllers
kaitlynkhan Nov 22, 2025
3627151
implemented the Staff views and Admin views
r7pt Nov 23, 2025
c74595e
Unit Tests Implementation
tlbuwi01 Nov 24, 2025
a580507
API errors fixed
r7pt Nov 24, 2025
54bdd28
Edited Integration Test Implementation
tlbuwi01 Nov 25, 2025
a916314
Fixed typos and logic errors
tlbuwi01 Nov 25, 2025
206cb00
Update test_app.py
tlbuwi01 Nov 25, 2025
6036374
Added refactors to models and controllers
andreaselenaa Nov 28, 2025
2550ae6
Merge pull request #1 from kaitlynkhan/hotfix
andreaselenaa Nov 29, 2025
1eb28cf
Merge branch 'API-implemented'
Dec 1, 2025
61ec530
Merge API-implemented into main after resolving conflicts
Dec 1, 2025
3703d82
Delete API_REFERENCE.md
tlbuwi01 Dec 2, 2025
e633d80
Delete REFACTORING_SUMMARY.md
tlbuwi01 Dec 2, 2025
34bc54f
Delete TEMPLATES_REFACTORING_SUMMARY.md
tlbuwi01 Dec 2, 2025
40b2392
Delete TESTING_GUIDE.md
tlbuwi01 Dec 2, 2025
33274ad
Delete TEST_REPORT.md
tlbuwi01 Dec 2, 2025
fca85cf
Delete VIEWS_REFACTORING_SUMMARY.md
tlbuwi01 Dec 2, 2025
773c3a9
api issue resolved
r7pt Dec 2, 2025
0acd6af
fixed auth error
r7pt Dec 3, 2025
e3a8a18
Add gunicorn to requirements.txt
r7pt Dec 3, 2025
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
2 changes: 1 addition & 1 deletion App/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def load_config(app, overrides):
app.config['UPLOADED_PHOTOS_DEST'] = "App/uploads"
app.config['JWT_ACCESS_COOKIE_NAME'] = 'access_token'
app.config["JWT_TOKEN_LOCATION"] = ["cookies", "headers"]
app.config["JWT_COOKIE_SECURE"] = True
app.config["JWT_COOKIE_SECURE"] = False
app.config["JWT_COOKIE_CSRF_PROTECT"] = False
app.config['FLASK_ADMIN_SWATCH'] = 'darkly'
for key in overrides:
Expand Down
40 changes: 35 additions & 5 deletions App/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
from .user import *
from .auth import *
from .initialize import *
from .admin import *
from .staff import *
# User management
from .user import (
create_user,
get_user,
get_user_by_username,
get_all_users,
get_all_users_json,
update_user
)

# Authentication
from .auth import login, loginCLI, logout

# Initialize database
from .initialize import initialize

# Staff actions
from .staff import (
get_combined_roster,
clock_in,
clock_out,
get_shift
)

# Admin schedule functions
from .admin import (
create_schedule,
add_shift,
auto_populate_schedule,
get_schedule_report
)

# Schedule controller (class)
from .schedule_controller import ScheduleController

57 changes: 18 additions & 39 deletions App/controllers/admin.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,37 @@
from App.models import Shift
from App.database import db
from datetime import datetime
from App.controllers.user import get_user

from App.models import Shift, Schedule
from App.database import db
from datetime import datetime
from App.controllers.user import get_user
from App.models.admin import Admin
from App.controllers.schedule_controller import ScheduleController

def create_schedule(admin_id, scheduleName): #Not sure why this was missing
def create_schedule(admin_id, schedule_name, user_id=None):
"""Allow an admin to create a new schedule."""
admin = get_user(admin_id)
if not admin or admin.role != "admin":
raise PermissionError("Only admins can create schedules")

new_schedule = Schedule(
created_by=admin_id,
name=scheduleName,
created_at=datetime.utcnow()
)

db.session.add(new_schedule)
db.session.commit()

return new_schedule
return ScheduleController.create_schedule(admin_id, schedule_name, user_id)

def schedule_shift(admin_id, staff_id, schedule_id, start_time, end_time):
def add_shift(admin_id, staff_id, schedule_id, start_time, end_time, shift_type="day"):
"""Allow an admin to manually add a shift."""
admin = get_user(admin_id)
staff = get_user(staff_id)

schedule = db.session.get(Schedule, schedule_id)

if not admin or admin.role != "admin":
raise PermissionError("Only admins can schedule shifts")
if not staff or staff.role != "staff":
raise ValueError("Invalid staff member")
if not schedule:
raise ValueError("Invalid schedule ID")

new_shift = Shift(
staff_id=staff_id,
schedule_id=schedule_id,
start_time=start_time,
end_time=end_time
)

db.session.add(new_shift)
db.session.commit()
return ScheduleController.add_shift(schedule_id, staff_id, start_time, end_time, shift_type)

return new_shift
def auto_populate_schedule(admin_id, schedule_id, strategy_name):
"""Allow an admin to auto-populate shifts using a strategy."""
admin = get_user(admin_id)
if not admin or admin.role != "admin":
raise PermissionError("Only admins can populate schedules")

return ScheduleController.auto_populate(schedule_id, strategy_name)

def get_shift_report(admin_id):
def get_schedule_report(admin_id, schedule_id):
"""Allow an admin to view the schedule report."""
admin = get_user(admin_id)
if not admin or admin.role != "admin":
raise PermissionError("Only admins can view shift reports")
raise PermissionError("Only admins can view schedule reports")

return [shift.get_json() for shift in Shift.query.order_by(Shift.start_time).all()]
return ScheduleController.get_Schedule_report(schedule_id)
66 changes: 42 additions & 24 deletions App/controllers/auth.py
Original file line number Diff line number Diff line change
@@ -1,80 +1,98 @@
from flask import jsonify
from flask_jwt_extended import (
create_access_token, jwt_required, JWTManager,
get_jwt_identity, verify_jwt_in_request
get_jwt_identity, set_access_cookies, verify_jwt_in_request
)
from App.models import User
from App.models import User, user
from App.database import db

def _get_user_by_username(username):
"""Fetch a user object by username."""
result = db.session.execute(db.select(User).filter_by(username=username))
return result.scalar_one_or_none()

def login(username, password):
result = db.session.execute(db.select(User).filter_by(username=username))
user = result.scalar_one_or_none()
if user and user.check_password(password):
# Store ONLY the user id as a string in JWT 'sub'
return create_access_token(identity=str(user.id))
return None
user = _get_user_by_username(username)
if user and user.check_password(password):
token = create_access_token(identity=user)
return token
return None

def loginCLI(username, password):
result = db.session.execute(db.select(User).filter_by(username=username))
user = result.scalar_one_or_none()
user = _get_user_by_username(username)

if user and user.check_password(password):


# Return existing token if already logged in
if user.active_token:
return {"message": "User already logged in", "token": user.active_token}

# Generate new token
token = create_access_token(identity=str(user.id))
user.active_token = token
db.session.commit()

return {"message": "Login successful", "token": token}

return {"message": "Invalid username or password"}


def logout(username):
result = db.session.execute(db.select(User).filter_by(username=username))
user = result.scalar_one_or_none()
user = _get_user_by_username(username)

if not user:
return {"message": "User not found"}

if not user.active_token:
return {"message": f"User {username} is not logged in"}
return {"message": f"User '{username}' is not logged in"}

user.active_token = None
db.session.commit()
return {"message": f"User {username} logged out successfully"}

return {"message": f"User '{username}' logged out successfully"}


def setup_jwt(app):
jwt = JWTManager(app)

# Always store a string user id in the JWT identity (sub)
# Always store user.id (as string) in JWT
@jwt.user_identity_loader
def user_identity_lookup(identity):
user_id = getattr(identity, "id", identity)
return str(user_id) if user_id is not None else None

# Automatically load user from JWT on request
@jwt.user_lookup_loader
def user_lookup_callback(_jwt_header, jwt_data):
identity = jwt_data["sub"]
identity = jwt_data.get("sub")
try:
user_id = int(identity)
return db.session.get(User, int(identity))
except (TypeError, ValueError):
return None
return db.session.get(User, user_id)

return jwt

# Context processor to make 'is_authenticated' available to all templates
def add_auth_context(app):
@app.context_processor
def inject_user():
try:
verify_jwt_in_request()
identity = get_jwt_identity()
user_id = int(identity) if identity is not None else None
current_user = db.session.get(User, user_id) if user_id is not None else None

current_user = (
db.session.get(User, user_id)
if user_id is not None else None
)

is_authenticated = current_user is not None
except Exception as e:
print(e)

except Exception:
# Invalid or missing JWT
is_authenticated = False
current_user = None
return dict(is_authenticated=is_authenticated, current_user=current_user)

return dict(
is_authenticated=is_authenticated,
current_user=current_user
)
34 changes: 19 additions & 15 deletions App/controllers/initialize.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from .user import create_user
from App.models.schedule import Schedule
from App.models.shift import Shift
from App.database import db


Expand All @@ -10,21 +12,23 @@ def initialize():
create_user('alice', 'alicepass', 'staff')
create_user('tim', 'timpass', 'user')

# db.session.commit()
db.session.commit()

# # adding dummy schedule data for testing Jane
# schedule = Schedule (
# name = "Morning Shift",
# created_by = 1
# )
# db.session.add(schedule)
# db.session.commit()
#adding dummy schedule data for testing Jane
schedule = Schedule (
name = "Morning Shift",
created_by = 1
)
db.session.add(schedule)
db.session.commit()

# # adding dummy shifts for Jane
# shift1 = Shift (
# schedule_id = schedule.id,
# staff_id = 2,
# start_time = "2024-10-01 08:00:00",
# end_time = "2024-10-01 12:00:00"
# )
# db.session.add(shift1)
shift1 = Shift (
schedule_id = schedule.id,
staff_id = 2,
start_time = "2024-10-01 08:00:00",
end_time = "2024-10-01 12:00:00"
)
db.session.add(shift1)

#shift2 = Shift(staff_id=2, schedule_id=schedule.id, start_time="2024-10-01 12:00:00", end_time="2024-10-01 16:00:00")
82 changes: 82 additions & 0 deletions App/controllers/schedule_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from App.database import db
from App.models.schedule import Schedule
from App.models.shift import Shift
from App.models import Staff, Admin
from datetime import datetime

# Import strategies
from App.models.strategies.even_distribution import EvenDistributionStrategy
from App.models.strategies.minimize_days import MinimizeDaysStrategy
from App.models.strategies.balance_day_night import BalanceDayNightStrategy

class ScheduleController:
"""Controller to manage schedules and auto-assign shifts using strategies."""

@staticmethod
def create_schedule(admin_id, name, user_id=None):
"""Create a new schedule, optionally for a specific user.
Note: Permission checking is done in admin controller."""
new_schedule = Schedule(
name=name,
created_by=admin_id,
user_id=user_id
)
db.session.add(new_schedule)
db.session.commit()
return new_schedule

@staticmethod
def add_shift(schedule_id, staff_id, start_time, end_time, shift_type="day"):
"""Add a shift for a specific staff to a schedule."""
schedule = db.session.get(Schedule, schedule_id)
staff = db.session.get(Staff, staff_id)
if not schedule or not staff:
raise ValueError("Invalid schedule or staff")

shift = Shift(
staff_id=staff_id,
schedule_id=schedule_id,
start_time=start_time,
end_time=end_time,
)
# Optional type attribute for day/night shifts
shift.type = shift_type

db.session.add(shift)
db.session.commit()
return shift

@staticmethod
def auto_populate(schedule_id, strategy_name):
"""Auto-populate the shifts of a schedule using a strategy."""
schedule = db.session.get(Schedule, schedule_id)
if not schedule:
raise ValueError("Schedule not found")

staff_list = Staff.query.all()
shift_list = schedule.shifts # Existing shifts in the schedule

# Assign strategy
if strategy_name == "even_distribution":
strategy = EvenDistributionStrategy()
elif strategy_name == "minimize_days":
strategy = MinimizeDaysStrategy()
elif strategy_name == "balance_day_night":
strategy = BalanceDayNightStrategy()
else:
raise ValueError("Invalid strategy name")

# Generate schedule using the strategy
updated_shifts = strategy.generate(staff_list, shift_list)

# Commit updated staff assignments
db.session.commit()
return updated_shifts

@staticmethod
def get_Schedule_report(schedule_id):
"""Return JSON data for a schedule and its shifts."""
schedule = db.session.get(Schedule, schedule_id)
if not schedule:
raise ValueError("Schedule not found")
return schedule.get_json()
Loading