From efe2d97bcf410ceaa66b12dd97d73760a04fc8fb Mon Sep 17 00:00:00 2001 From: Jesse Snyder Date: Sat, 24 May 2025 02:47:16 -0600 Subject: [PATCH 1/2] ai --- .gitignore | 4 +++ CLAUDE.md | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 134e751..fc5d31a 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,7 @@ GitHub.sublime-settings !.vscode/launch.json !.vscode/extensions.json .history + +# ai +.claude +CLAUDE.local.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3e9c5fc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Oscarr is a Discord bot that integrates with Plex and Ombi to provide a Discord interface for managing media requests. The project consists of a Django backend that serves both as an admin interface and the bot's data layer. + +## Development Commands + +All commands should be run from `/packages/django-app/` directory using the `just` task runner: + +### Setup & Initialization +- `just init-dcp` - Initialize Docker volumes and build images +- `just dcp-generate-secret-key` - Generate Django secret key for .env +- `just generate-pg-secret-key` - Generate PostgreSQL encryption key +- `just create-superuser` - Create Django admin user +- `just dcp-load-dev-data` - Load fixture data (admin:password login) + +### Development Workflow +- `just dcp-up-all` - Start all containers (web + db) +- `just dcp-migrate` - Run database migrations +- `just start-discord-bot` - Launch Discord bot in container +- `just sync-with-plex` - Sync movie data from Plex server + +### Testing & Code Quality +- `just dcp-run-tests` - Run pytest test suite (excludes integration tests) +- `just dcp-format` - Format Python code with autopep8 + +### Backup & Data Management +- `just dcp-dumpdata` - Export database to JSON +- `just create-json-backup` - Create timestamped JSON backup +- `just create-pgdump` - Create PostgreSQL dump backup + +## Architecture + +### Django Apps Structure +- `core/` - User management with custom User model, Discord/Ombi integration +- `plex/` - Plex server data models and sync functionality +- `movie_requests/` - Movie request handling commands +- `watchbot/` - Additional bot functionality +- `discordbot/` - Discord bot implementation with commands +- `services/` - External API integrations (Ombi, Radarr, TMDB) + +### Key Patterns +- **Repository Pattern**: Base repository classes in `common/repositories/` +- **Management Commands**: Custom Django commands for bot operations and data sync +- **Signal Integration**: Django signals for automated workflows (e.g., Ombi user creation) +- **Encrypted Fields**: Uses django-pgcrypto-fields for sensitive data + +### Discord Bot Commands +- `/search_tmdb` - Search TMDB for movies with request buttons +- `/request ` - Request movie by TMDB ID +- `/search_plex` - Search Plex library (by title, actor, director, producer) +- `/get_random` - Get random movie from Plex +- `/bacon from: to: ` - Show actor connections through movies + +## Environment Setup + +The project requires a `.env` file in `/packages/django-app/` with configuration for: +- Django secret keys +- PostgreSQL credentials +- Plex server connection +- Ombi API credentials +- Discord bot token + +See `.env.template` for required environment variables. + +## Technology Stack + +- **Backend**: Django 4.2.5, PostgreSQL 15.4 +- **Bot**: discord.py +- **Media APIs**: PlexAPI, TMDB Simple +- **External Services**: Ombi, Radarr +- **Testing**: pytest with Django integration +- **Containerization**: Docker Compose +- **Task Runner**: Just (justfile) +- **Code Quality**: autopep8, pylint, flake8 + +## Development Notes + +- Uses Python 3.11.4 with Pipenv for dependency management +- PostgreSQL with pgcrypto extension for encrypted fields +- All Django management commands should be run via `./bin/dcp-django-admin.sh` for Docker +- Tests exclude integration tests by default (use `-m "not integration"`) +- The bot runs as a separate management command (`start_discord_bot`) +- Plex sync should be run periodically to keep movie data current \ No newline at end of file From da2400e86c76664d8f40b4a23333e75b410c711c Mon Sep 17 00:00:00 2001 From: Jesse Snyder Date: Sun, 25 May 2025 16:04:54 -0600 Subject: [PATCH 2/2] wip but error from ombi --- .idea/misc.xml | 2 +- .idea/poc.iml | 2 +- CLAUDE.md | 15 +++++- TODO.md | 6 +++ packages/django-app/app/core/apps.py | 4 ++ packages/django-app/app/core/signals.py | 35 ++++++++++++++ packages/django-app/app/services/ombi.py | 59 ++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 TODO.md create mode 100644 packages/django-app/app/core/signals.py diff --git a/.idea/misc.xml b/.idea/misc.xml index 9b3221b..e0b3bb2 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/poc.iml b/.idea/poc.iml index cdc9ae2..5b5795b 100644 --- a/.idea/poc.iml +++ b/.idea/poc.iml @@ -17,7 +17,7 @@ - + diff --git a/CLAUDE.md b/CLAUDE.md index 3e9c5fc..9ead3df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,7 @@ All commands should be run from `/packages/django-app/` directory using the `jus - `just generate-pg-secret-key` - Generate PostgreSQL encryption key - `just create-superuser` - Create Django admin user - `just dcp-load-dev-data` - Load fixture data (admin:password login) +- `just setup-local-python-venv` - Create local Python virtual environment and install dependencies ### Development Workflow - `just dcp-up-all` - Start all containers (web + db) @@ -32,6 +33,11 @@ All commands should be run from `/packages/django-app/` directory using the `jus - `just create-json-backup` - Create timestamped JSON backup - `just create-pgdump` - Create PostgreSQL dump backup +### Utility Commands +- `just update-oscarr` - Pull latest code, build, migrate, and restart containers +- `just dcp-cleanup` - Stop and remove all containers and volumes +- `just dcp-build-images` - Build both web and database Docker images + ## Architecture ### Django Apps Structure @@ -84,4 +90,11 @@ See `.env.template` for required environment variables. - All Django management commands should be run via `./bin/dcp-django-admin.sh` for Docker - Tests exclude integration tests by default (use `-m "not integration"`) - The bot runs as a separate management command (`start_discord_bot`) -- Plex sync should be run periodically to keep movie data current \ No newline at end of file +- Plex sync should be run periodically to keep movie data current + +## Local Development Setup + +1. **Python Environment**: Use pyenv to install Python 3.11.4, then run `just setup-local-python-venv` +2. **Docker Setup**: Run `just init-dcp` to initialize Docker containers and volumes +3. **Database**: Run `just dcp-migrate` to apply database migrations +4. **Development Data**: Run `just dcp-load-dev-data` to load test data (admin:password) \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..6d4d2e6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +# TODOs 5/23/2025 + +- [ ] create ombi user on user create +- [ ] add bot command that only admin can use to create user + - [ ] would be extra nice to be able to select discord user from discord + interface to get discord user id for the new user when sending command diff --git a/packages/django-app/app/core/apps.py b/packages/django-app/app/core/apps.py index 8115ae6..3bcfc64 100644 --- a/packages/django-app/app/core/apps.py +++ b/packages/django-app/app/core/apps.py @@ -4,3 +4,7 @@ class CoreConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'core' + + def ready(self): + """Import signal handlers when the app is ready.""" + import core.signals diff --git a/packages/django-app/app/core/signals.py b/packages/django-app/app/core/signals.py new file mode 100644 index 0000000..5ea8cff --- /dev/null +++ b/packages/django-app/app/core/signals.py @@ -0,0 +1,35 @@ +import logging +from django.db.models.signals import post_save +from django.dispatch import receiver + +from core.models import User +from services.ombi import Ombi + +logger = logging.getLogger(__name__) + + +@receiver(post_save, sender=User) +def create_ombi_user(sender, instance, created, **kwargs): + """ + Signal handler to create an Ombi user when a Django user is created, + and set the ombi_uid field on the Django user. + """ + if created and not instance.ombi_uid: + try: + logger.info(f"Creating Ombi user for Django user {instance.nickname}") + + # Create user in Ombi + response = Ombi.create_user(username=instance.nickname) + + # Extract user ID from response and update Django user + if response and 'id' in response: + ombi_uid = response['id'] + logger.info(f"Updating Django user {instance.nickname} with Ombi UID {ombi_uid}") + + # Update the user without triggering the signal again + User.objects.filter(id=instance.id).update(ombi_uid=ombi_uid) + else: + logger.error(f"Failed to get Ombi UID from response: {response}") + + except Exception as e: + logger.error(f"Error creating Ombi user for {instance.nickname}: {str(e)}") diff --git a/packages/django-app/app/services/ombi.py b/packages/django-app/app/services/ombi.py index 873570b..5754eee 100644 --- a/packages/django-app/app/services/ombi.py +++ b/packages/django-app/app/services/ombi.py @@ -44,3 +44,62 @@ def create_request(cls, data: dict) -> dict: data = res.json() return data + + @classmethod + def create_user(cls, username: str, email: str = None, password: str = None) -> dict: + """ + Creates a user in Ombi and returns the created user data including the user ID. + + Args: + username: The username for the new Ombi user + email: Optional email for the user + password: Optional password for the user + + Returns: + dict: The created user data containing the Ombi user ID + """ + endpoint = f'{cls.base_url}/Identity' + logger.info(f"Ombi create user endpoint: {endpoint}") + headers = { + 'content-type': 'application/json', + 'ApiKey': settings.OMBI_API_KEY, + } + + # Create user data + user_data = { + "userName": username, + "userType": 1, + "movieRequestLimit": 0, + "episodeRequestLimit": 0, + "musicRequestLimit": 0, + "streamingCountry": "us", + "movieRequestLimitType": 0, + "musicRequestLimitType": 0, + "episodeRequestLimitType": 0, + "hasLoggedIn": False, + "source": "local" + } + + # Add optional fields if provided + if email: + user_data["emailAddress"] = email + if password: + user_data["password"] = password + + logger.info(f"Creating Ombi user: {username}") + logger.info(f"Request data: {json.dumps(user_data, indent=2)}") + logger.info(f"Request headers: {headers}") + + res = requests.post( + endpoint, + auth=HTTPBasicAuth(settings.SEEDBOX_UN, settings.SEEDBOX_PW), + data=json.dumps(user_data), + headers=headers) + + if not res.ok: + logger.error(f"Failed to create Ombi user: {res.status_code} - {res.text}") + raise Exception(f"Failed to create Ombi user: {res.status_code} - {res.text}") + + data = res.json() + logger.info(f"Successfully created Ombi user: {data}") + return data