Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""add pending_role_ids to notifications

Revision ID: 0f529c470393
Revises: 98df8301ae46
Create Date: 2026-02-16 22:35:37.433719

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '0f529c470393'
down_revision: Union[str, Sequence[str], None] = '98df8301ae46'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('notification', sa.Column('pending_role_ids', sa.JSON(), nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('notification', 'pending_role_ids')
# ### end Alembic commands ###
2 changes: 2 additions & 0 deletions database_utils/models/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ..utils.timezone_utils import now_gt

from datetime import datetime
from typing import Optional
import uuid

# Association table for many-to-many relationship between Role and Permission
Expand Down Expand Up @@ -146,6 +147,7 @@ class Notification(Base):
email = Column(String, nullable=False, unique=True)
age = Column(Integer, nullable=False)
password_hash = Column(String, nullable=False)
pending_role_ids: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
# --- [END] Possible User data: Add User Fields ---

company_id: Mapped[uuid.UUID] = mapped_column(Uuid, ForeignKey("company.id", ondelete="CASCADE"), nullable=False)
Expand Down
28 changes: 28 additions & 0 deletions database_utils/utils/audit_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,31 @@ def log_custom_operation(
details=details,
ip_address=ip_address
)


def serialize_for_audit(data: dict) -> dict:
"""Convert a model column dict to JSON-safe types for audit logging.

Handles UUID, datetime, date, and Decimal types that are not
JSON-serializable by default.

Args:
data: Dictionary of model column values to serialize.

Returns:
A new dictionary with all non-JSON-serializable values converted to str.
"""
from uuid import UUID
from datetime import datetime, date
from decimal import Decimal

def _convert(v):
if isinstance(v, (UUID, datetime, date, Decimal)):
return str(v)
if isinstance(v, dict):
return {k: _convert(val) for k, val in v.items()}
if isinstance(v, list):
return [_convert(item) for item in v]
return v

return {k: _convert(v) for k, v in data.items()}
20 changes: 20 additions & 0 deletions database_utils/utils/pagination_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import math


def compute_pagination(page: int, page_size: int, total_count: int) -> tuple[int, int, int]:
"""Compute pagination values for a SQLAlchemy query.

Args:
page: Current page number (1-indexed).
page_size: Number of records per page.
total_count: Total number of records matching the query filters.

Returns:
tuple: (skip, total_count, total_pages)
- skip: number of records to offset
- total_count: same as input (passed through for convenience)
- total_pages: total number of pages (minimum 1)
"""
skip = (page - 1) * page_size
total_pages = math.ceil(total_count / page_size) if total_count > 0 else 1
return skip, total_count, total_pages
Loading