Adding more shit

This commit is contained in:
James Pattinson
2025-11-10 15:42:09 +00:00
parent f1c4ff19d6
commit 43b13ef52d
10 changed files with 682 additions and 21 deletions

View File

@@ -4,11 +4,12 @@ from typing import List
from datetime import date, timedelta
from ...core.database import get_db
from ...models.models import Membership, MembershipStatus, User, MembershipTier
from ...models.models import Membership, MembershipStatus, User, MembershipTier, Payment, PaymentStatus
from ...schemas import (
MembershipCreate, MembershipUpdate, MembershipResponse, MessageResponse
)
from ...api.dependencies import get_current_active_user, get_admin_user
from ...services.email_service import email_service
router = APIRouter()
@@ -112,6 +113,11 @@ async def update_membership(
)
update_data = membership_update.model_dump(exclude_unset=True)
was_activated = False
# Check if status is being changed to ACTIVE
if update_data.get("status") == MembershipStatus.ACTIVE and membership.status != MembershipStatus.ACTIVE:
was_activated = True
for field, value in update_data.items():
setattr(membership, field, value)
@@ -119,6 +125,32 @@ async def update_membership(
db.commit()
db.refresh(membership)
# Send activation email if membership was just activated
if was_activated:
try:
# Get the most recent payment for this membership
recent_payment = db.query(Payment).filter(
Payment.membership_id == membership.id,
Payment.status == PaymentStatus.COMPLETED
).order_by(Payment.payment_date.desc()).first()
payment_amount = recent_payment.amount if recent_payment else membership.tier.annual_fee
payment_method = recent_payment.payment_method.value if recent_payment else "N/A"
# Send activation email (non-blocking)
await email_service.send_membership_activation_email(
to_email=membership.user.email,
first_name=membership.user.first_name,
membership_tier=membership.tier.name,
annual_fee=membership.tier.annual_fee,
payment_amount=payment_amount,
payment_method=payment_method,
renewal_date=membership.end_date.strftime("%d %B %Y")
)
except Exception as e:
# Log error but don't fail the membership update
print(f"Failed to send membership activation email: {e}")
return membership

View File

@@ -4,11 +4,12 @@ from typing import List
from datetime import datetime
from ...core.database import get_db
from ...models.models import Payment, PaymentStatus, User, Membership
from ...models.models import Payment, PaymentStatus, User, Membership, MembershipStatus
from ...schemas import (
PaymentCreate, PaymentUpdate, PaymentResponse, MessageResponse
)
from ...api.dependencies import get_current_active_user, get_admin_user
from ...services.email_service import email_service
router = APIRouter()
@@ -114,6 +115,31 @@ async def update_payment(
db.commit()
db.refresh(payment)
# If payment was just marked as completed and has an associated membership,
# activate the membership and send activation email
if (update_data.get("status") == PaymentStatus.COMPLETED and
payment.membership_id and
payment.membership.status == MembershipStatus.PENDING):
# Activate the membership
payment.membership.status = MembershipStatus.ACTIVE
db.commit()
# Send activation email (non-blocking)
try:
await email_service.send_membership_activation_email(
to_email=payment.membership.user.email,
first_name=payment.membership.user.first_name,
membership_tier=payment.membership.tier.name,
annual_fee=payment.membership.tier.annual_fee,
payment_amount=payment.amount,
payment_method=payment.payment_method.value,
renewal_date=payment.membership.end_date.strftime("%d %B %Y")
)
except Exception as e:
# Log error but don't fail the payment update
print(f"Failed to send membership activation email: {e}")
return payment
@@ -152,6 +178,7 @@ async def record_manual_payment(
)
# Verify membership if provided
membership = None
if payment_data.membership_id:
membership = db.query(Membership).filter(
Membership.id == payment_data.membership_id,
@@ -178,4 +205,24 @@ async def record_manual_payment(
db.commit()
db.refresh(payment)
# If payment has an associated membership that's pending, activate it and send email
if membership and membership.status == MembershipStatus.PENDING:
membership.status = MembershipStatus.ACTIVE
db.commit()
# Send activation email (non-blocking)
try:
await email_service.send_membership_activation_email(
to_email=membership.user.email,
first_name=membership.user.first_name,
membership_tier=membership.tier.name,
annual_fee=membership.tier.annual_fee,
payment_amount=payment.amount,
payment_method=payment.payment_method.value,
renewal_date=membership.end_date.strftime("%d %B %Y")
)
except Exception as e:
# Log error but don't fail the payment creation
print(f"Failed to send membership activation email: {e}")
return payment

View File

@@ -7,7 +7,7 @@ from ...models.models import MembershipTier
from ...schemas import (
MembershipTierCreate, MembershipTierUpdate, MembershipTierResponse, MessageResponse
)
from ...api.dependencies import get_current_active_user, get_admin_user
from ...api.dependencies import get_current_active_user, get_admin_user, get_super_admin_user
router = APIRouter()
@@ -47,10 +47,10 @@ async def get_membership_tier(
@router.post("/", response_model=MembershipTierResponse, status_code=status.HTTP_201_CREATED)
async def create_membership_tier(
tier_data: MembershipTierCreate,
current_user = Depends(get_admin_user),
current_user = Depends(get_super_admin_user),
db: Session = Depends(get_db)
):
"""Create a new membership tier (admin only)"""
"""Create a new membership tier (super admin only)"""
# Check if tier with same name exists
existing_tier = db.query(MembershipTier).filter(
MembershipTier.name == tier_data.name
@@ -74,10 +74,10 @@ async def create_membership_tier(
async def update_membership_tier(
tier_id: int,
tier_update: MembershipTierUpdate,
current_user = Depends(get_admin_user),
current_user = Depends(get_super_admin_user),
db: Session = Depends(get_db)
):
"""Update membership tier (admin only)"""
"""Update membership tier (super admin only)"""
tier = db.query(MembershipTier).filter(MembershipTier.id == tier_id).first()
if not tier:
raise HTTPException(
@@ -99,10 +99,10 @@ async def update_membership_tier(
@router.delete("/{tier_id}", response_model=MessageResponse)
async def delete_membership_tier(
tier_id: int,
current_user = Depends(get_admin_user),
current_user = Depends(get_super_admin_user),
db: Session = Depends(get_db)
):
"""Delete membership tier (admin only)"""
"""Delete membership tier (super admin only)"""
tier = db.query(MembershipTier).filter(MembershipTier.id == tier_id).first()
if not tier:
raise HTTPException(

View File

@@ -1,5 +1,6 @@
import httpx
from typing import List, Optional
from datetime import datetime
from ..core.config import settings
@@ -141,6 +142,83 @@ class EmailService:
return await self.send_email(to_email, subject, html_body, text_body)
async def send_membership_activation_email(
self,
to_email: str,
first_name: str,
membership_tier: str,
annual_fee: float,
payment_amount: float,
payment_method: str,
renewal_date: str
) -> dict:
"""Send membership activation email with payment details and renewal date"""
subject = f"Your {settings.APP_NAME} Membership is Now Active!"
html_body = f"""
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<h2 style="color: #28a745;">Welcome to {settings.APP_NAME}!</h2>
<p>Hello {first_name},</p>
<p>Great news! Your membership has been successfully activated. You now have full access to all the benefits of your membership tier.</p>
<div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #28a745;">
<h3 style="margin-top: 0; color: #28a745;">Membership Details</h3>
<p style="margin: 8px 0;"><strong>Membership Tier:</strong> {membership_tier}</p>
<p style="margin: 8px 0;"><strong>Annual Fee:</strong> £{annual_fee:.2f}</p>
<p style="margin: 8px 0;"><strong>Next Renewal Date:</strong> {renewal_date}</p>
</div>
<div style="background-color: #e9ecef; padding: 20px; border-radius: 8px; margin: 20px 0;">
<h3 style="margin-top: 0; color: #495057;">Payment Information</h3>
<p style="margin: 8px 0;"><strong>Amount Paid:</strong> £{payment_amount:.2f}</p>
<p style="margin: 8px 0;"><strong>Payment Method:</strong> {payment_method}</p>
<p style="margin: 8px 0;"><strong>Payment Date:</strong> {datetime.now().strftime('%d %B %Y')}</p>
</div>
<p>Your membership will automatically renew on <strong>{renewal_date}</strong> unless you choose to cancel it. You can manage your membership settings in your account dashboard.</p>
<p>If you have any questions about your membership or need assistance, please don't hesitate to contact us.</p>
<p>Welcome to the {settings.APP_NAME} community!</p>
<p>Best regards,<br>
<strong>{settings.APP_NAME} Team</strong></p>
</body>
</html>
"""
text_body = f"""
Welcome to {settings.APP_NAME}!
Hello {first_name},
Great news! Your membership has been successfully activated. You now have full access to all the benefits of your membership tier.
MEMBERSHIP DETAILS
------------------
Membership Tier: {membership_tier}
Annual Fee: £{annual_fee:.2f}
Next Renewal Date: {renewal_date}
PAYMENT INFORMATION
-------------------
Amount Paid: £{payment_amount:.2f}
Payment Method: {payment_method}
Payment Date: {datetime.now().strftime('%d %B %Y')}
Your membership will automatically renew on {renewal_date} unless you choose to cancel it. You can manage your membership settings in your account dashboard.
If you have any questions about your membership or need assistance, please don't hesitate to contact us.
Welcome to the {settings.APP_NAME} community!
Best regards,
{settings.APP_NAME} Team
"""
return await self.send_email(to_email, subject, html_body, text_body)
async def send_membership_renewal_reminder(
self,
to_email: str,