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
7 changes: 3 additions & 4 deletions api/routers/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
NotificationResponse,
NotificationStatsResponse,
)
from api.timeutils import utcnow

router = APIRouter(tags=["notifications"])

Expand Down Expand Up @@ -147,8 +148,6 @@ async def mark_notification_read(
Sets ``opened_at`` to now (idempotent — does nothing if already set).
The mobile Inbox calls this when the user taps a row.
"""
from api.timeutils import utcnow

notification = db.query(Notification).filter(Notification.id == notification_id).first()
if not notification:
raise HTTPException(status_code=404, detail="Notification not found")
Expand Down Expand Up @@ -282,9 +281,9 @@ async def get_organization_notification_stats(
# Verify admin belongs to organization
verify_org_member(admin, org_id)

from datetime import datetime, timedelta
from datetime import timedelta

cutoff_date = datetime.utcnow() - timedelta(days=days)
cutoff_date = utcnow() - timedelta(days=days)

# Total notifications by status
status_counts = (
Expand Down
3 changes: 2 additions & 1 deletion api/routers/recurring_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
generate_occurrences,
validate_series_duration,
)
from api.timeutils import utcnow

router = APIRouter(tags=["recurring-events"])

Expand Down Expand Up @@ -454,7 +455,7 @@ def update_series_template(
if role_requirements is not None:
series.role_requirements = role_requirements

series.updated_at = datetime.utcnow()
series.updated_at = utcnow()

db.commit()
db.refresh(series)
Expand Down
13 changes: 6 additions & 7 deletions api/routers/sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
send_broadcast_message,
send_event_reminder,
)
from api.timeutils import utcnow

router = APIRouter(prefix="/api/sms", tags=["sms"])

Expand Down Expand Up @@ -166,9 +167,9 @@ def send_verification_code(
db=db, person_id=request.person_id, phone_number=request.phone_number
)

from datetime import datetime, timedelta
from datetime import timedelta

expires_at = (datetime.utcnow() + timedelta(minutes=10)).isoformat()
expires_at = (utcnow() + timedelta(minutes=10)).isoformat()

return VerificationCodeResponse(
message="Verification code sent via SMS",
Expand Down Expand Up @@ -308,7 +309,6 @@ def get_sms_usage_stats(

Returns current month usage with budget tracking.
"""
from datetime import datetime

from api.models import SmsUsage

Expand All @@ -318,7 +318,7 @@ def get_sms_usage_stats(
raise HTTPException(status_code=403, detail="Access denied: wrong organization")

# Get current month usage
current_month = datetime.utcnow().strftime("%Y-%m")
current_month = utcnow().strftime("%Y-%m")

usage = (
db.query(SmsUsage)
Expand Down Expand Up @@ -548,7 +548,6 @@ async def twilio_delivery_status_webhook(request: Request, db: Session = Depends
raise HTTPException(status_code=422, detail="Missing required fields")

# Update message status in database
from datetime import datetime

from api.models import SmsMessage

Expand All @@ -560,9 +559,9 @@ async def twilio_delivery_status_webhook(request: Request, db: Session = Depends
sms_message.status = message_status

if message_status == "delivered":
sms_message.delivered_at = datetime.utcnow()
sms_message.delivered_at = utcnow()
elif message_status in ["failed", "undelivered"]:
sms_message.failed_at = datetime.utcnow()
sms_message.failed_at = utcnow()
sms_message.error_message = form_data.get("ErrorMessage", "Unknown error")

db.commit()
Expand Down
3 changes: 1 addition & 2 deletions api/routers/solutions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Solutions router - view and export generated solutions."""

from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response, status
from sqlalchemy.orm import Session
Expand Down Expand Up @@ -167,7 +166,7 @@ def create_manual_solution(
soft_score=solution_data.get("soft_score", 0.0),
health_score=solution_data.get("health_score", 0.0),
metrics=solution_data.get("metrics", {}),
created_at=datetime.utcnow(),
created_at=utcnow(),
)

db.add(new_solution)
Expand Down
21 changes: 11 additions & 10 deletions api/services/billing_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
SubscriptionEvent,
UsageMetrics,
)
from api.timeutils import utcnow
from api.utils.stripe_client import StripeClient


Expand Down Expand Up @@ -213,7 +214,7 @@ def update_volunteer_count(self, org_id: str) -> UsageMetrics | None:
metric.current_value = volunteer_count
metric.plan_limit = plan_limit
metric.percentage_used = percentage_used
metric.last_updated = datetime.utcnow()
metric.last_updated = utcnow()
else:
metric = UsageMetrics(
org_id=org_id,
Expand Down Expand Up @@ -524,13 +525,13 @@ def start_trial(
previous_plan = subscription.plan_tier

# Calculate trial end date
trial_end = datetime.utcnow() + timedelta(days=trial_days)
trial_end = utcnow() + timedelta(days=trial_days)

# Update subscription to trial status
subscription.plan_tier = plan_tier
subscription.status = "trialing"
subscription.trial_end_date = trial_end
subscription.current_period_start = datetime.utcnow()
subscription.current_period_start = utcnow()
subscription.current_period_end = trial_end

self.db.commit()
Expand Down Expand Up @@ -594,7 +595,7 @@ def auto_downgrade_expired_trials(self) -> dict[str, Any]:
# Called daily by Celery task
"""
try:
now = datetime.utcnow()
now = utcnow()

# Find all expired trials
expired_trials = (
Expand Down Expand Up @@ -893,7 +894,7 @@ def _calculate_prorated_amount(self, subscription: Subscription, new_billing_cyc
- Positive: Charge (monthly → annual)
- Negative: Credit (annual → monthly)
"""
now = datetime.utcnow()
now = utcnow()
current_cycle = subscription.billing_cycle
plan_tier = subscription.plan_tier

Expand Down Expand Up @@ -1003,7 +1004,7 @@ def downgrade_subscription(
}

# Calculate effective date (end of current period)
effective_date = subscription.current_period_end or datetime.utcnow()
effective_date = subscription.current_period_end or utcnow()

# Calculate credit for unused time
credit_amount = self._calculate_downgrade_credit(
Expand All @@ -1016,7 +1017,7 @@ def downgrade_subscription(
"effective_date": effective_date.isoformat(),
"credit_amount_cents": credit_amount,
"reason": reason,
"scheduled_at": datetime.utcnow().isoformat(),
"scheduled_at": utcnow().isoformat(),
}

self.db.commit()
Expand Down Expand Up @@ -1062,7 +1063,7 @@ def _calculate_downgrade_credit(self, subscription: Subscription, new_plan_tier:
Returns:
int: Credit amount in cents
"""
now = datetime.utcnow()
now = utcnow()
current_plan = subscription.plan_tier
billing_cycle = subscription.billing_cycle

Expand Down Expand Up @@ -1119,7 +1120,7 @@ def apply_pending_downgrades(self) -> dict[str, Any]:
}
"""
try:
now = datetime.utcnow()
now = utcnow()
applied_downgrades = []

# Find all subscriptions with pending downgrades
Expand Down Expand Up @@ -1395,7 +1396,7 @@ def reactivate_subscription(self, org_id: str, admin_id: str | None = None) -> d
"message": "Subscription was not cancelled or retention period has expired",
}

now = datetime.utcnow()
now = utcnow()

if now > org.data_retention_until:
return {
Expand Down
3 changes: 1 addition & 2 deletions api/services/email_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,6 @@ def _update_notification_status(
sendgrid_message_id: Optional SendGrid message ID
error_message: Optional error message
"""
from datetime import datetime

notification.status = status

Expand All @@ -412,7 +411,7 @@ def _update_notification_status(
notification.error_message = error_message

if status == "sent":
notification.sent_at = datetime.utcnow()
notification.sent_at = utcnow()

db.commit()

Expand Down
6 changes: 3 additions & 3 deletions api/services/notification_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import logging
import os
import secrets
from datetime import datetime
from typing import Any

from sqlalchemy.orm import Session
Expand All @@ -24,6 +23,7 @@
Person,
)
from api.tasks.notifications import send_email_task
from api.timeutils import utcnow

logger = logging.getLogger(__name__)
_TRUTHY_VALUES = {"1", "true", "yes", "on"}
Expand Down Expand Up @@ -141,7 +141,7 @@ def create_assignment_notifications(
"assignment_id": assignment.id,
"role": assignment.role if hasattr(assignment, "role") else None,
},
created_at=datetime.utcnow(),
created_at=utcnow(),
)
db.add(notification)
db.flush() # Flush to get notification ID
Expand Down Expand Up @@ -217,7 +217,7 @@ def create_notification(
status=NotificationStatus.PENDING,
event_id=event_id,
template_data=template_data or {},
created_at=datetime.utcnow(),
created_at=utcnow(),
)
db.add(notification)
db.flush()
Expand Down
25 changes: 13 additions & 12 deletions api/services/sms_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import os
import random
import re
from datetime import datetime, timedelta
from datetime import timedelta
from typing import Any

from jinja2 import Template
Expand All @@ -28,6 +28,7 @@
SmsTemplate,
SmsVerificationCode,
)
from api.timeutils import utcnow
from api.utils.cost_tracker import CostTracker
from api.utils.quiet_hours import QuietHours
from api.utils.sms_rate_limiter import SmsRateLimiter
Expand Down Expand Up @@ -130,7 +131,7 @@ def send_sms(
twilio_message_sid=twilio_message.sid,
cost_cents=cost_cents,
is_urgent=is_urgent,
sent_at=datetime.utcnow(),
sent_at=utcnow(),
)
db.add(sms_message)

Expand Down Expand Up @@ -165,7 +166,7 @@ def send_sms(
error_message=str(e),
cost_cents=0,
is_urgent=is_urgent,
failed_at=datetime.utcnow(),
failed_at=utcnow(),
)
db.add(sms_message)
db.commit()
Expand Down Expand Up @@ -350,7 +351,7 @@ def generate_verification_code(self, db: Session, person_id: int, phone_number:
"""
# 1. Generate random 6-digit code
code = random.randint(100000, 999999)
expires_at = datetime.utcnow() + timedelta(minutes=10)
expires_at = utcnow() + timedelta(minutes=10)

# 2. Delete any existing verification codes for this person
db.query(SmsVerificationCode).filter(SmsVerificationCode.person_id == person_id).delete()
Expand Down Expand Up @@ -413,7 +414,7 @@ def verify_code(self, db: Session, person_id: int, code: int) -> dict[str, Any]:
raise ValueError("No verification code found. Please request a new code.")

# 2. Check expiration (10 minutes)
if datetime.utcnow() > verification.expires_at:
if utcnow() > verification.expires_at:
db.delete(verification)
db.commit()
raise ValueError("Verification code expired. Please request a new code.")
Expand Down Expand Up @@ -443,15 +444,15 @@ def verify_code(self, db: Session, person_id: int, code: int) -> dict[str, Any]:
phone_number=verification.phone_number,
verified=True,
notification_types=["assignment", "reminder", "change", "cancellation"],
opt_in_date=datetime.utcnow(),
opt_in_date=utcnow(),
language="en",
timezone="UTC",
)
db.add(sms_pref)
else:
sms_pref.verified = True
sms_pref.phone_number = verification.phone_number
sms_pref.opt_in_date = datetime.utcnow()
sms_pref.opt_in_date = utcnow()

# 6. Delete verification code record
db.delete(verification)
Expand Down Expand Up @@ -545,7 +546,7 @@ def process_incoming_reply(
event_id=None,
action_taken=action,
twilio_message_sid=twilio_message_sid,
processed_at=datetime.utcnow(),
processed_at=utcnow(),
)
db.add(sms_reply)
db.commit()
Expand Down Expand Up @@ -578,7 +579,7 @@ def process_yes_reply(
.join(Event)
.filter(
Assignment.person_id == person_id,
Event.datetime >= datetime.utcnow(),
Event.datetime >= utcnow(),
)
.order_by(Event.datetime.asc())
.first()
Expand Down Expand Up @@ -624,7 +625,7 @@ def process_no_reply(self, db: Session, person_id: int, original_message_id: int
.join(Event)
.filter(
Assignment.person_id == person_id,
Event.datetime >= datetime.utcnow(),
Event.datetime >= utcnow(),
)
.order_by(Event.datetime.asc())
.first()
Expand Down Expand Up @@ -672,7 +673,7 @@ def process_stop_reply(self, db: Session, person_id: int) -> str:
return "You are not subscribed to SMS notifications."

# 2. Update opt_out_date (TCPA compliance)
sms_pref.opt_out_date = datetime.utcnow()
sms_pref.opt_out_date = utcnow()
db.commit()

# 3. Return opt-out confirmation
Expand Down Expand Up @@ -704,7 +705,7 @@ def process_start_reply(self, db: Session, person_id: int) -> str:

# 3. Clear opt_out_date to re-enable
sms_pref.opt_out_date = None
sms_pref.opt_in_date = datetime.utcnow() # Update opt-in date
sms_pref.opt_in_date = utcnow() # Update opt-in date
db.commit()

# 4. Return re-enablement confirmation
Expand Down
Loading
Loading