feat: Implement initial schema and add various API, service, and management command enhancements across the application.

This commit is contained in:
pacnpal
2026-01-01 15:13:01 -05:00
parent c95f99ca10
commit b243b17af7
413 changed files with 11164 additions and 17433 deletions

View File

@@ -22,20 +22,14 @@ class Command(BaseCommand):
# Check SocialAccount
self.stdout.write("\nChecking SocialAccount table:")
for account in SocialAccount.objects.all():
self.stdout.write(
f"ID: {account.pk}, Provider: {account.provider}, UID: {account.uid}"
)
self.stdout.write(f"ID: {account.pk}, Provider: {account.provider}, UID: {account.uid}")
# Check SocialToken
self.stdout.write("\nChecking SocialToken table:")
for token in SocialToken.objects.all():
self.stdout.write(
f"ID: {token.pk}, Account: {token.account}, App: {token.app}"
)
self.stdout.write(f"ID: {token.pk}, Account: {token.account}, App: {token.app}")
# Check Site
self.stdout.write("\nChecking Site table:")
for site in Site.objects.all():
self.stdout.write(
f"ID: {site.pk}, Domain: {site.domain}, Name: {site.name}"
)
self.stdout.write(f"ID: {site.pk}, Domain: {site.domain}, Name: {site.name}")

View File

@@ -17,6 +17,4 @@ class Command(BaseCommand):
self.stdout.write(f"Name: {app.name}")
self.stdout.write(f"Client ID: {app.client_id}")
self.stdout.write(f"Secret: {app.secret}")
self.stdout.write(
f"Sites: {', '.join(str(site.domain) for site in app.sites.all())}"
)
self.stdout.write(f"Sites: {', '.join(str(site.domain) for site in app.sites.all())}")

View File

@@ -15,14 +15,9 @@ class Command(BaseCommand):
# Remove migration records
cursor.execute("DELETE FROM django_migrations WHERE app='socialaccount'")
cursor.execute(
"DELETE FROM django_migrations WHERE app='accounts' "
"AND name LIKE '%social%'"
)
cursor.execute("DELETE FROM django_migrations WHERE app='accounts' " "AND name LIKE '%social%'")
# Reset sequences
cursor.execute("DELETE FROM sqlite_sequence WHERE name LIKE '%social%'")
self.stdout.write(
self.style.SUCCESS("Successfully cleaned up social auth configuration")
)
self.stdout.write(self.style.SUCCESS("Successfully cleaned up social auth configuration"))

View File

@@ -18,24 +18,18 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS(f"Deleted {count} test users"))
# Delete test reviews
reviews = ParkReview.objects.filter(
user__username__in=["testuser", "moderator"]
)
reviews = ParkReview.objects.filter(user__username__in=["testuser", "moderator"])
count = reviews.count()
reviews.delete()
self.stdout.write(self.style.SUCCESS(f"Deleted {count} test reviews"))
# Delete test photos - both park and ride photos
park_photos = ParkPhoto.objects.filter(
uploader__username__in=["testuser", "moderator"]
)
park_photos = ParkPhoto.objects.filter(uploader__username__in=["testuser", "moderator"])
park_count = park_photos.count()
park_photos.delete()
self.stdout.write(self.style.SUCCESS(f"Deleted {park_count} test park photos"))
ride_photos = RidePhoto.objects.filter(
uploader__username__in=["testuser", "moderator"]
)
ride_photos = RidePhoto.objects.filter(uploader__username__in=["testuser", "moderator"])
ride_count = ride_photos.count()
ride_photos.delete()
self.stdout.write(self.style.SUCCESS(f"Deleted {ride_count} test ride photos"))

View File

@@ -37,18 +37,12 @@ class Command(BaseCommand):
provider="google",
defaults={
"name": "Google",
"client_id": (
"135166769591-nopcgmo0fkqfqfs9qe783a137mtmcrt2."
"apps.googleusercontent.com"
),
"client_id": ("135166769591-nopcgmo0fkqfqfs9qe783a137mtmcrt2." "apps.googleusercontent.com"),
"secret": "GOCSPX-Wd_0Ue0Ue0Ue0Ue0Ue0Ue0Ue0Ue",
},
)
if not created:
google_app.client_id = (
"135166769591-nopcgmo0fkqfqfs9qe783a137mtmcrt2."
"apps.googleusercontent.com"
)
google_app.client_id = "135166769591-nopcgmo0fkqfqfs9qe783a137mtmcrt2." "apps.googleusercontent.com"
google_app.secret = "GOCSPX-Wd_0Ue0Ue0Ue0Ue0Ue0Ue0Ue0Ue"
google_app.save()
google_app.sites.add(site)

View File

@@ -14,9 +14,7 @@ class Command(BaseCommand):
)
user.set_password("testpass123")
user.save()
self.stdout.write(
self.style.SUCCESS(f"Created test user: {user.get_username()}")
)
self.stdout.write(self.style.SUCCESS(f"Created test user: {user.get_username()}"))
else:
self.stdout.write(self.style.WARNING("Test user already exists"))
@@ -47,11 +45,7 @@ class Command(BaseCommand):
# Add user to moderator group
moderator.groups.add(moderator_group)
self.stdout.write(
self.style.SUCCESS(
f"Created moderator user: {moderator.get_username()}"
)
)
self.stdout.write(self.style.SUCCESS(f"Created moderator user: {moderator.get_username()}"))
else:
self.stdout.write(self.style.WARNING("Moderator user already exists"))

View File

@@ -17,9 +17,7 @@ class Command(BaseCommand):
help = "Delete a user while preserving all their submissions"
def add_arguments(self, parser):
parser.add_argument(
"username", nargs="?", type=str, help="Username of the user to delete"
)
parser.add_argument("username", nargs="?", type=str, help="Username of the user to delete")
parser.add_argument(
"--user-id",
type=str,
@@ -30,9 +28,7 @@ class Command(BaseCommand):
action="store_true",
help="Show what would be deleted without actually deleting",
)
parser.add_argument(
"--force", action="store_true", help="Skip confirmation prompt"
)
parser.add_argument("--force", action="store_true", help="Skip confirmation prompt")
def handle(self, *args, **options):
username = options.get("username")
@@ -52,7 +48,7 @@ class Command(BaseCommand):
user = User.objects.get(username=username) if username else User.objects.get(user_id=user_id)
except User.DoesNotExist:
identifier = username or user_id
raise CommandError(f'User "{identifier}" does not exist')
raise CommandError(f'User "{identifier}" does not exist') from None
# Check if user can be deleted
can_delete, reason = UserDeletionService.can_delete_user(user)
@@ -61,27 +57,13 @@ class Command(BaseCommand):
# Count submissions
submission_counts = {
"park_reviews": getattr(
user, "park_reviews", user.__class__.objects.none()
).count(),
"ride_reviews": getattr(
user, "ride_reviews", user.__class__.objects.none()
).count(),
"uploaded_park_photos": getattr(
user, "uploaded_park_photos", user.__class__.objects.none()
).count(),
"uploaded_ride_photos": getattr(
user, "uploaded_ride_photos", user.__class__.objects.none()
).count(),
"top_lists": getattr(
user, "top_lists", user.__class__.objects.none()
).count(),
"edit_submissions": getattr(
user, "edit_submissions", user.__class__.objects.none()
).count(),
"photo_submissions": getattr(
user, "photo_submissions", user.__class__.objects.none()
).count(),
"park_reviews": getattr(user, "park_reviews", user.__class__.objects.none()).count(),
"ride_reviews": getattr(user, "ride_reviews", user.__class__.objects.none()).count(),
"uploaded_park_photos": getattr(user, "uploaded_park_photos", user.__class__.objects.none()).count(),
"uploaded_ride_photos": getattr(user, "uploaded_ride_photos", user.__class__.objects.none()).count(),
"top_lists": getattr(user, "top_lists", user.__class__.objects.none()).count(),
"edit_submissions": getattr(user, "edit_submissions", user.__class__.objects.none()).count(),
"photo_submissions": getattr(user, "photo_submissions", user.__class__.objects.none()).count(),
}
total_submissions = sum(submission_counts.values())
@@ -98,9 +80,7 @@ class Command(BaseCommand):
self.stdout.write(self.style.WARNING("\nSubmissions to preserve:"))
for submission_type, count in submission_counts.items():
if count > 0:
self.stdout.write(
f' {submission_type.replace("_", " ").title()}: {count}'
)
self.stdout.write(f' {submission_type.replace("_", " ").title()}: {count}')
self.stdout.write(f"\nTotal submissions: {total_submissions}")
@@ -111,9 +91,7 @@ class Command(BaseCommand):
)
)
else:
self.stdout.write(
self.style.WARNING("\nNo submissions found for this user.")
)
self.stdout.write(self.style.WARNING("\nNo submissions found for this user."))
if dry_run:
self.stdout.write(self.style.SUCCESS("\n[DRY RUN] No changes were made."))
@@ -136,11 +114,7 @@ class Command(BaseCommand):
try:
result = UserDeletionService.delete_user_preserve_submissions(user)
self.stdout.write(
self.style.SUCCESS(
f'\nSuccessfully deleted user "{result["deleted_user"]["username"]}"'
)
)
self.stdout.write(self.style.SUCCESS(f'\nSuccessfully deleted user "{result["deleted_user"]["username"]}"'))
preserved_count = sum(result["preserved_submissions"].values())
if preserved_count > 0:
@@ -154,9 +128,7 @@ class Command(BaseCommand):
self.stdout.write(self.style.WARNING("\nPreservation Summary:"))
for submission_type, count in result["preserved_submissions"].items():
if count > 0:
self.stdout.write(
f' {submission_type.replace("_", " ").title()}: {count}'
)
self.stdout.write(f' {submission_type.replace("_", " ").title()}: {count}')
except Exception as e:
raise CommandError(f"Error deleting user: {str(e)}")
raise CommandError(f"Error deleting user: {str(e)}") from None

View File

@@ -7,12 +7,5 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
with connection.cursor() as cursor:
cursor.execute(
"DELETE FROM django_migrations WHERE app='rides' "
"AND name='0001_initial';"
)
self.stdout.write(
self.style.SUCCESS(
"Successfully removed rides.0001_initial from migration history"
)
)
cursor.execute("DELETE FROM django_migrations WHERE app='rides' " "AND name='0001_initial';")
self.stdout.write(self.style.SUCCESS("Successfully removed rides.0001_initial from migration history"))

View File

@@ -34,6 +34,4 @@ class Command(BaseCommand):
secret=os.getenv("DISCORD_CLIENT_SECRET"),
)
discord_app.sites.add(site)
self.stdout.write(
f"Created Discord app with client_id: {discord_app.client_id}"
)
self.stdout.write(f"Created Discord app with client_id: {discord_app.client_id}")

View File

@@ -47,9 +47,7 @@ class Command(BaseCommand):
help = "Generate avatars for letters A-Z and numbers 0-9"
def handle(self, *args, **kwargs):
characters = [chr(i) for i in range(65, 91)] + [
str(i) for i in range(10)
] # A-Z and 0-9
characters = [chr(i) for i in range(65, 91)] + [str(i) for i in range(10)] # A-Z and 0-9
for char in characters:
generate_avatar(char)
self.stdout.write(self.style.SUCCESS(f"Generated avatar for {char}"))

View File

@@ -11,6 +11,4 @@ class Command(BaseCommand):
for profile in profiles:
# This will trigger the avatar generation logic in the save method
profile.save()
self.stdout.write(
self.style.SUCCESS(f"Regenerated avatar for {profile.user.username}")
)
self.stdout.write(self.style.SUCCESS(f"Regenerated avatar for {profile.user.username}"))

View File

@@ -69,18 +69,18 @@ class Command(BaseCommand):
# Security: Using Django ORM instead of raw SQL for user creation
user = User.objects.create_superuser(
username='admin',
email='admin@thrillwiki.com',
password='admin',
role='SUPERUSER',
username="admin",
email="admin@thrillwiki.com",
password="admin",
role="SUPERUSER",
)
# Create profile using ORM
UserProfile.objects.create(
user=user,
display_name='Admin',
pronouns='they/them',
bio='ThrillWiki Administrator',
display_name="Admin",
pronouns="they/them",
bio="ThrillWiki Administrator",
)
self.stdout.write("Superuser created.")

View File

@@ -30,9 +30,7 @@ class Command(BaseCommand):
google_app = SocialApp.objects.create(
provider="google",
name="Google",
client_id=(
"135166769591-nopcgmo0fkqfqfs9qe783a137mtmcrt2.apps.googleusercontent.com"
),
client_id=("135166769591-nopcgmo0fkqfqfs9qe783a137mtmcrt2.apps.googleusercontent.com"),
secret="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",
)
google_app.sites.add(site)

View File

@@ -12,13 +12,7 @@ class Command(BaseCommand):
cursor.execute("DELETE FROM socialaccount_socialapp_sites")
# Reset sequences
cursor.execute(
"DELETE FROM sqlite_sequence WHERE name='socialaccount_socialapp'"
)
cursor.execute(
"DELETE FROM sqlite_sequence WHERE name='socialaccount_socialapp_sites'"
)
cursor.execute("DELETE FROM sqlite_sequence WHERE name='socialaccount_socialapp'")
cursor.execute("DELETE FROM sqlite_sequence WHERE name='socialaccount_socialapp_sites'")
self.stdout.write(
self.style.SUCCESS("Successfully reset social auth configuration")
)
self.stdout.write(self.style.SUCCESS("Successfully reset social auth configuration"))

View File

@@ -30,9 +30,7 @@ class Command(BaseCommand):
user.is_staff = True
user.save()
self.stdout.write(
self.style.SUCCESS("Successfully set up groups and permissions")
)
self.stdout.write(self.style.SUCCESS("Successfully set up groups and permissions"))
# Print summary
for group in Group.objects.all():

View File

@@ -10,7 +10,5 @@ class Command(BaseCommand):
Site.objects.all().delete()
# Create default site
site = Site.objects.create(
id=1, domain="localhost:8000", name="ThrillWiki Development"
)
site = Site.objects.create(id=1, domain="localhost:8000", name="ThrillWiki Development")
self.stdout.write(self.style.SUCCESS(f"Created site: {site.domain}"))

View File

@@ -49,27 +49,15 @@ class Command(BaseCommand):
discord_client_secret,
]
):
self.stdout.write(
self.style.ERROR("Missing required environment variables")
)
self.stdout.write(
f"DEBUG: google_client_id is None: {google_client_id is None}"
)
self.stdout.write(
f"DEBUG: google_client_secret is None: {google_client_secret is None}"
)
self.stdout.write(
f"DEBUG: discord_client_id is None: {discord_client_id is None}"
)
self.stdout.write(
f"DEBUG: discord_client_secret is None: {discord_client_secret is None}"
)
self.stdout.write(self.style.ERROR("Missing required environment variables"))
self.stdout.write(f"DEBUG: google_client_id is None: {google_client_id is None}")
self.stdout.write(f"DEBUG: google_client_secret is None: {google_client_secret is None}")
self.stdout.write(f"DEBUG: discord_client_id is None: {discord_client_id is None}")
self.stdout.write(f"DEBUG: discord_client_secret is None: {discord_client_secret is None}")
return
# Get or create the default site
site, _ = Site.objects.get_or_create(
id=1, defaults={"domain": "localhost:8000", "name": "localhost"}
)
site, _ = Site.objects.get_or_create(id=1, defaults={"domain": "localhost:8000", "name": "localhost"})
# Set up Google
google_app, created = SocialApp.objects.get_or_create(
@@ -92,11 +80,7 @@ class Command(BaseCommand):
google_app.save()
self.stdout.write("DEBUG: Successfully updated Google app")
else:
self.stdout.write(
self.style.ERROR(
"Google client_id or secret is None, skipping update."
)
)
self.stdout.write(self.style.ERROR("Google client_id or secret is None, skipping update."))
google_app.sites.add(site)
# Set up Discord
@@ -120,11 +104,7 @@ class Command(BaseCommand):
discord_app.save()
self.stdout.write("DEBUG: Successfully updated Discord app")
else:
self.stdout.write(
self.style.ERROR(
"Discord client_id or secret is None, skipping update."
)
)
self.stdout.write(self.style.ERROR("Discord client_id or secret is None, skipping update."))
discord_app.sites.add(site)
self.stdout.write(self.style.SUCCESS("Successfully set up social auth apps"))

View File

@@ -42,6 +42,4 @@ class Command(BaseCommand):
for app in SocialApp.objects.all():
self.stdout.write(f"- {app.name} ({app.provider}): {app.client_id}")
self.stdout.write(
self.style.SUCCESS(f"\nTotal social apps: {SocialApp.objects.count()}")
)
self.stdout.write(self.style.SUCCESS(f"\nTotal social apps: {SocialApp.objects.count()}"))

View File

@@ -40,9 +40,7 @@ class Command(BaseCommand):
# Show callback URL
callback_url = "http://localhost:8000/accounts/discord/login/callback/"
self.stdout.write(
"\nCallback URL to configure in Discord Developer Portal:"
)
self.stdout.write("\nCallback URL to configure in Discord Developer Portal:")
self.stdout.write(callback_url)
# Show frontend login URL

View File

@@ -18,6 +18,4 @@ class Command(BaseCommand):
# Add all sites
for site in sites:
app.sites.add(site)
self.stdout.write(
f"Added sites: {', '.join(site.domain for site in sites)}"
)
self.stdout.write(f"Added sites: {', '.join(site.domain for site in sites)}")

View File

@@ -22,17 +22,13 @@ class Command(BaseCommand):
# Show callback URL
callback_url = "http://localhost:8000/accounts/discord/login/callback/"
self.stdout.write(
"\nCallback URL to configure in Discord Developer Portal:"
)
self.stdout.write("\nCallback URL to configure in Discord Developer Portal:")
self.stdout.write(callback_url)
# Show OAuth2 settings
self.stdout.write("\nOAuth2 settings in settings.py:")
discord_settings = settings.SOCIALACCOUNT_PROVIDERS.get("discord", {})
self.stdout.write(
f"PKCE Enabled: {discord_settings.get('OAUTH_PKCE_ENABLED', False)}"
)
self.stdout.write(f"PKCE Enabled: {discord_settings.get('OAUTH_PKCE_ENABLED', False)}")
self.stdout.write(f"Scopes: {discord_settings.get('SCOPE', [])}")
except SocialApp.DoesNotExist: