feat: complete monorepo structure with frontend and shared resources

- Add complete backend/ directory with full Django application
- Add frontend/ directory with Vite + TypeScript setup ready for Next.js
- Add comprehensive shared/ directory with:
  - Complete documentation and memory-bank archives
  - Media files and avatars (letters, park/ride images)
  - Deployment scripts and automation tools
  - Shared types and utilities
- Add architecture/ directory with migration guides
- Configure pnpm workspace for monorepo development
- Update .gitignore to exclude .django_tailwind_cli/ build artifacts
- Preserve all historical documentation in shared/docs/memory-bank/
- Set up proper structure for full-stack development with shared resources
This commit is contained in:
pacnpal
2025-08-23 18:40:07 -04:00
parent b0e0678590
commit d504d41de2
762 changed files with 142636 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.test import RequestFactory, Client
from allauth.account.models import EmailAddress
from apps.accounts.adapters import CustomAccountAdapter
from django.conf import settings
import uuid
User = get_user_model()
class Command(BaseCommand):
help = "Test all email flows in the application"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.factory = RequestFactory()
# Disable CSRF for testing
self.client = Client(enforce_csrf_checks=False)
self.adapter = CustomAccountAdapter()
self.site = Site.objects.get_current()
# Generate unique test data
unique_id = str(uuid.uuid4())[:8]
self.test_username = f"testuser_{unique_id}"
self.test_email = f"test_{unique_id}@thrillwiki.com"
self.test_password = "[PASSWORD-REMOVED]"
self.new_password = "[PASSWORD-REMOVED]"
# Add testserver to ALLOWED_HOSTS
if "testserver" not in settings.ALLOWED_HOSTS:
settings.ALLOWED_HOSTS.append("testserver")
def handle(self, *args, **options):
self.stdout.write("Starting email flow tests...\n")
# Clean up any existing test users
User.objects.filter(email__endswith="@thrillwiki.com").delete()
# Test registration email
self.test_registration()
# Create a test user for other flows
user = User.objects.create_user(
username=f"testuser2_{str(uuid.uuid4())[:8]}",
email=f"test2_{str(uuid.uuid4())[:8]}@thrillwiki.com",
password=self.test_password,
)
EmailAddress.objects.create(
user=user, email=user.email, primary=True, verified=True
)
# Log in the test user
self.client.force_login(user)
# Test other flows
self.test_password_change(user)
self.test_email_change(user)
self.test_password_reset(user)
# Cleanup
User.objects.filter(email__endswith="@thrillwiki.com").delete()
self.stdout.write(self.style.SUCCESS("All email flow tests completed!\n"))
def test_registration(self):
"""Test registration email flow"""
self.stdout.write("Testing registration email...")
try:
# Use dj-rest-auth registration endpoint
response = self.client.post(
"/api/auth/registration/",
{
"username": self.test_username,
"email": self.test_email,
"password1": self.test_password,
"password2": self.test_password,
},
content_type="application/json",
)
if response.status_code in [200, 201, 204]:
self.stdout.write(
self.style.SUCCESS("Registration email test passed!\n")
)
else:
self.stdout.write(
self.style.WARNING(
f"Registration returned status {
response.status_code}: {
response.content.decode()}\n"
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"Registration email test failed: {
str(e)}\n"
)
)
def test_password_change(self, user):
"""Test password change using dj-rest-auth"""
self.stdout.write("Testing password change email...")
try:
response = self.client.post(
"/api/auth/password/change/",
{
"old_password": self.test_password,
"new_password1": self.new_password,
"new_password2": self.new_password,
},
content_type="application/json",
)
if response.status_code == 200:
self.stdout.write(
self.style.SUCCESS("Password change email test passed!\n")
)
else:
self.stdout.write(
self.style.WARNING(
f"Password change returned status {
response.status_code}: {
response.content.decode()}\n"
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"Password change email test failed: {
str(e)}\n"
)
)
def test_email_change(self, user):
"""Test email change verification"""
self.stdout.write("Testing email change verification...")
try:
new_email = f"newemail_{str(uuid.uuid4())[:8]}@thrillwiki.com"
response = self.client.post(
"/api/auth/email/",
{"email": new_email},
content_type="application/json",
)
if response.status_code == 200:
self.stdout.write(
self.style.SUCCESS("Email change verification test passed!\n")
)
else:
self.stdout.write(
self.style.WARNING(
f"Email change returned status {
response.status_code}: {
response.content.decode()}\n"
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"Email change verification test failed: {
str(e)}\n"
)
)
def test_password_reset(self, user):
"""Test password reset using dj-rest-auth"""
self.stdout.write("Testing password reset email...")
try:
# Request password reset
response = self.client.post(
"/api/auth/password/reset/",
{"email": user.email},
content_type="application/json",
)
if response.status_code == 200:
self.stdout.write(
self.style.SUCCESS("Password reset email test passed!\n")
)
else:
self.stdout.write(
self.style.WARNING(
f"Password reset returned status {
response.status_code}: {
response.content.decode()}\n"
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"Password reset email test failed: {
str(e)}\n"
)
)

View File

@@ -0,0 +1,244 @@
from django.core.management.base import BaseCommand
from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.conf import settings
import requests
import os
from apps.email_service.models import EmailConfiguration
from apps.email_service.services import EmailService
from apps.email_service.backends import ForwardEmailBackend
class Command(BaseCommand):
help = "Test the email service functionality"
def add_arguments(self, parser):
parser.add_argument(
"--to",
type=str,
help="Recipient email address (optional, defaults to current user's email)",
)
parser.add_argument(
"--api-key",
type=str,
help="ForwardEmail API key (optional, will use configured value)",
)
parser.add_argument(
"--from-email",
type=str,
help="Sender email address (optional, will use configured value)",
)
def get_config(self):
"""Get email configuration from database or environment"""
try:
site = Site.objects.get(id=settings.SITE_ID)
config = EmailConfiguration.objects.get(site=site)
return {
"api_key": config.api_key,
"from_email": config.default_from_email,
"site": site,
}
except (Site.DoesNotExist, EmailConfiguration.DoesNotExist):
# Try environment variables
api_key = os.environ.get("FORWARD_EMAIL_API_KEY")
from_email = os.environ.get("FORWARD_EMAIL_FROM")
if not api_key or not from_email:
self.stdout.write(
self.style.WARNING(
"No configuration found in database or environment variables.\n"
"Please either:\n"
"1. Configure email settings in Django admin, or\n"
"2. Set environment variables FORWARD_EMAIL_API_KEY and FORWARD_EMAIL_FROM, or\n"
"3. Provide --api-key and --from-email arguments"
)
)
return None
return {
"api_key": api_key,
"from_email": from_email,
"site": Site.objects.get(id=settings.SITE_ID),
}
def handle(self, *args, **options):
self.stdout.write(self.style.SUCCESS("Starting email service tests..."))
# Get configuration
config = self.get_config()
if not config and not (options["api_key"] and options["from_email"]):
self.stdout.write(
self.style.ERROR("No email configuration available. Tests aborted.")
)
return
# Use provided values or fall back to config
api_key = options["api_key"] or config["api_key"]
from_email = options["from_email"] or config["from_email"]
site = config["site"]
# If no recipient specified, use the from_email address for testing
to_email = options["to"] or "test@thrillwiki.com"
self.stdout.write(self.style.SUCCESS("Using configuration:"))
self.stdout.write(f" From: {from_email}")
self.stdout.write(f" To: {to_email}")
self.stdout.write(f' API Key: {"*" * len(api_key)}')
self.stdout.write(f" Site: {site.domain}")
try:
# 1. Test site configuration
config = self.test_site_configuration(api_key, from_email)
# 2. Test direct service
self.test_email_service_directly(to_email, config.site)
# 3. Test API endpoint
self.test_api_endpoint(to_email)
# 4. Test Django email backend
self.test_email_backend(to_email, config.site)
self.stdout.write(
self.style.SUCCESS("\nAll tests completed successfully! 🎉")
)
except Exception as e:
self.stdout.write(self.style.ERROR(f"\nTests failed: {str(e)}"))
def test_site_configuration(self, api_key, from_email):
"""Test creating and retrieving site configuration"""
self.stdout.write("\nTesting site configuration...")
try:
# Get or create default site
site = Site.objects.get_or_create(
id=settings.SITE_ID,
defaults={"domain": "example.com", "name": "example.com"},
)[0]
# Create or update email configuration
config, created = EmailConfiguration.objects.update_or_create(
site=site,
defaults={
"api_key": api_key,
"default_from_email": from_email,
},
)
action = "Created new" if created else "Updated existing"
self.stdout.write(self.style.SUCCESS(f"{action} site configuration"))
return config
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"✗ Site configuration failed: {
str(e)}"
)
)
raise
def test_api_endpoint(self, to_email):
"""Test sending email via the API endpoint"""
self.stdout.write("\nTesting API endpoint...")
try:
# Make request to the API endpoint
response = requests.post(
"http://127.0.0.1:8000/api/email/send-email/",
json={
"to": to_email,
"subject": "Test Email via API",
"text": "This is a test email sent via the API endpoint.",
},
headers={
"Content-Type": "application/json",
},
timeout=60,
)
if response.status_code == 200:
self.stdout.write(self.style.SUCCESS("✓ API endpoint test successful"))
else:
self.stdout.write(
self.style.ERROR(
f"✗ API endpoint test failed with status {
response.status_code}: {
response.text}"
)
)
raise Exception(f"API test failed: {response.text}")
except requests.exceptions.ConnectionError:
self.stdout.write(
self.style.ERROR(
"✗ API endpoint test failed: Could not connect to server. "
"Make sure the Django development server is running."
)
)
raise Exception("Could not connect to Django server")
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"✗ API endpoint test failed: {
str(e)}"
)
)
raise
def test_email_backend(self, to_email, site):
"""Test sending email via Django's email backend"""
self.stdout.write("\nTesting Django email backend...")
try:
# Create a connection with site context
backend = ForwardEmailBackend(fail_silently=False, site=site)
# Debug output
self.stdout.write(
f" Debug: Using from_email: {
site.email_config.default_from_email}"
)
self.stdout.write(f" Debug: Using to_email: {to_email}")
send_mail(
subject="Test Email via Backend",
message="This is a test email sent via the Django email backend.",
from_email=site.email_config.default_from_email, # Explicitly set from_email
recipient_list=[to_email],
fail_silently=False,
connection=backend,
)
self.stdout.write(self.style.SUCCESS("✓ Email backend test successful"))
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"✗ Email backend test failed: {
str(e)}"
)
)
raise
def test_email_service_directly(self, to_email, site):
"""Test sending email directly via EmailService"""
self.stdout.write("\nTesting EmailService directly...")
try:
response = EmailService.send_email(
to=to_email,
subject="Test Email via Service",
text="This is a test email sent directly via the EmailService.",
site=site,
)
self.stdout.write(
self.style.SUCCESS("✓ Direct EmailService test successful")
)
return response
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"✗ Direct EmailService test failed: {
str(e)}"
)
)
raise