diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py new file mode 100644 index 00000000..4ce00e01 --- /dev/null +++ b/apps/accounts/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 5.1.6 on 2025-03-02 00:01 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('channels', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('avatar_config', models.JSONField(blank=True, default=dict, null=True)), + ('channel_groups', models.ManyToManyField(blank=True, related_name='users', to='channels.channelgroup')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/apps/channels/migrations/0001_initial.py b/apps/channels/migrations/0001_initial.py new file mode 100644 index 00000000..2e3990e2 --- /dev/null +++ b/apps/channels/migrations/0001_initial.py @@ -0,0 +1,61 @@ +# Generated by Django 5.1.6 on 2025-03-02 00:01 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('core', '0001_initial'), + ('m3u', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ChannelGroup', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ], + ), + migrations.CreateModel( + name='Stream', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(default='Default Stream', max_length=255)), + ('url', models.URLField()), + ('custom_url', models.URLField(blank=True, max_length=2000, null=True)), + ('logo_url', models.URLField(blank=True, max_length=2000, null=True)), + ('tvg_id', models.CharField(blank=True, max_length=255, null=True)), + ('local_file', models.FileField(blank=True, null=True, upload_to='uploads/')), + ('current_viewers', models.PositiveIntegerField(default=0)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('group_name', models.CharField(blank=True, max_length=255, null=True)), + ('m3u_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='streams', to='m3u.m3uaccount')), + ('stream_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='streams', to='core.streamprofile')), + ], + options={ + 'verbose_name': 'Stream', + 'verbose_name_plural': 'Streams', + 'ordering': ['-updated_at'], + }, + ), + migrations.CreateModel( + name='Channel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('channel_number', models.IntegerField()), + ('channel_name', models.CharField(max_length=255)), + ('logo_url', models.URLField(blank=True, max_length=2000, null=True)), + ('logo_file', models.ImageField(blank=True, null=True, upload_to='logos/')), + ('tvg_id', models.CharField(blank=True, max_length=255, null=True)), + ('tvg_name', models.CharField(blank=True, max_length=255, null=True)), + ('stream_profile', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='channels', to='core.streamprofile')), + ('channel_group', models.ForeignKey(blank=True, help_text='Channel group this channel belongs to.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='channels', to='channels.channelgroup')), + ('streams', models.ManyToManyField(blank=True, related_name='channels', to='channels.stream')), + ], + ), + ] diff --git a/apps/dashboard/migrations/0001_initial.py b/apps/dashboard/migrations/0001_initial.py new file mode 100644 index 00000000..1ce7b141 --- /dev/null +++ b/apps/dashboard/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 5.1.6 on 2025-03-02 00:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Settings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('server_name', models.CharField(default='Dispatcharr', max_length=255)), + ('time_zone', models.CharField(default='UTC', max_length=50)), + ('default_logo_url', models.URLField(blank=True, null=True)), + ('max_concurrent_streams', models.PositiveIntegerField(default=10)), + ('auto_backup_frequency', models.CharField(choices=[('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly')], default='weekly', max_length=50)), + ('enable_debug_logs', models.BooleanField(default=False)), + ('schedules_direct_username', models.CharField(blank=True, max_length=255, null=True)), + ('schedules_direct_password', models.CharField(blank=True, max_length=255, null=True)), + ('schedules_direct_update_frequency', models.CharField(choices=[('12h', 'Every 12 Hours'), ('daily', 'Daily')], default='daily', max_length=50)), + ('schedules_direct_api_key', models.CharField(blank=True, max_length=255, null=True)), + ('transcoding_bitrate', models.PositiveIntegerField(default=2000)), + ('transcoding_audio_codec', models.CharField(choices=[('aac', 'AAC'), ('mp3', 'MP3')], default='aac', max_length=50)), + ('transcoding_resolution', models.CharField(choices=[('720p', '720p'), ('1080p', '1080p')], default='1080p', max_length=50)), + ('failover_behavior', models.CharField(choices=[('sequential', 'Sequential'), ('random', 'Random')], default='sequential', max_length=50)), + ('stream_health_check_frequency', models.PositiveIntegerField(default=5)), + ('email_notifications', models.BooleanField(default=False)), + ('webhook_url', models.URLField(blank=True, null=True)), + ('cpu_alert_threshold', models.PositiveIntegerField(default=90)), + ('memory_alert_threshold', models.PositiveIntegerField(default=90)), + ('hdhr_integration', models.BooleanField(default=True)), + ('custom_api_endpoints', models.JSONField(blank=True, null=True)), + ('backup_path', models.CharField(default='backups/', max_length=255)), + ('backup_frequency', models.CharField(choices=[('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly')], default='weekly', max_length=50)), + ('ffmpeg_path', models.CharField(default='/usr/bin/ffmpeg', max_length=255)), + ('custom_transcoding_flags', models.TextField(blank=True, null=True)), + ('celery_worker_concurrency', models.PositiveIntegerField(default=4)), + ], + ), + ] diff --git a/apps/epg/migrations/0001_initial.py b/apps/epg/migrations/0001_initial.py new file mode 100644 index 00000000..dfb9c0f0 --- /dev/null +++ b/apps/epg/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 5.1.6 on 2025-03-02 00:01 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='EPGData', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tvg_id', models.CharField(blank=True, max_length=255, null=True)), + ('channel_name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='EPGSource', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True)), + ('source_type', models.CharField(choices=[('xmltv', 'XMLTV URL'), ('schedules_direct', 'Schedules Direct API')], max_length=20)), + ('url', models.URLField(blank=True, null=True)), + ('api_key', models.CharField(blank=True, max_length=255, null=True)), + ('is_active', models.BooleanField(default=True)), + ], + ), + migrations.CreateModel( + name='ProgramData', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start_time', models.DateTimeField()), + ('end_time', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('sub_title', models.CharField(blank=True, max_length=255, null=True)), + ('description', models.TextField(blank=True, null=True)), + ('tvg_id', models.CharField(blank=True, max_length=255, null=True)), + ('epg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='programs', to='epg.epgdata')), + ], + ), + ] diff --git a/apps/hdhr/migrations/0001_initial.py b/apps/hdhr/migrations/0001_initial.py new file mode 100644 index 00000000..826c036d --- /dev/null +++ b/apps/hdhr/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.6 on 2025-03-02 00:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='HDHRDevice', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('friendly_name', models.CharField(default='Dispatcharr HDHomeRun', max_length=100)), + ('device_id', models.CharField(max_length=32, unique=True)), + ('tuner_count', models.PositiveIntegerField(default=3)), + ], + ), + ] diff --git a/apps/m3u/migrations/0001_initial.py b/apps/m3u/migrations/0001_initial.py new file mode 100644 index 00000000..c78afaa1 --- /dev/null +++ b/apps/m3u/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 5.1.6 on 2025-03-02 00:01 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ServerGroup', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Unique name for this server group.', max_length=100, unique=True)), + ], + ), + migrations.CreateModel( + name='M3UAccount', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Unique name for this M3U account', max_length=255, unique=True)), + ('server_url', models.URLField(blank=True, help_text='The base URL of the M3U server (optional if a file is uploaded)', null=True)), + ('uploaded_file', models.FileField(blank=True, null=True, upload_to='m3u_uploads/')), + ('max_streams', models.PositiveIntegerField(default=0, help_text='Maximum number of concurrent streams (0 for unlimited)')), + ('is_active', models.BooleanField(default=True, help_text='Set to false to deactivate this M3U account')), + ('created_at', models.DateTimeField(auto_now_add=True, help_text='Time when this account was created')), + ('updated_at', models.DateTimeField(auto_now=True, help_text='Time when this account was last updated')), + ('user_agent', models.ForeignKey(blank=True, help_text='The User-Agent associated with this M3U account.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='m3u_accounts', to='core.useragent')), + ('server_group', models.ForeignKey(blank=True, help_text='The server group this M3U account belongs to', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='m3u_accounts', to='m3u.servergroup')), + ], + ), + migrations.CreateModel( + name='M3UFilter', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('filter_type', models.CharField(choices=[('group', 'Group Title'), ('name', 'Stream Name')], default='group', help_text='Filter based on either group title or stream name.', max_length=50)), + ('regex_pattern', models.CharField(help_text='A regex pattern to match streams or groups.', max_length=200)), + ('exclude', models.BooleanField(default=True, help_text='If True, matching items are excluded; if False, only matches are included.')), + ('m3u_account', models.ForeignKey(help_text='The M3U account this filter is applied to.', on_delete=django.db.models.deletion.CASCADE, related_name='filters', to='m3u.m3uaccount')), + ], + ), + migrations.CreateModel( + name='M3UAccountProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Name for the M3U account profile', max_length=255)), + ('is_default', models.BooleanField(default=False, help_text='Set to false to deactivate this profile')), + ('max_streams', models.PositiveIntegerField(default=0, help_text='Maximum number of concurrent streams (0 for unlimited)')), + ('is_active', models.BooleanField(default=True, help_text='Set to false to deactivate this profile')), + ('search_pattern', models.CharField(max_length=255)), + ('replace_pattern', models.CharField(max_length=255)), + ('current_viewers', models.PositiveIntegerField(default=0)), + ('m3u_account', models.ForeignKey(help_text='The M3U account this profile belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='profiles', to='m3u.m3uaccount')), + ], + options={ + 'constraints': [models.UniqueConstraint(fields=('m3u_account', 'name'), name='unique_account_profile_name')], + }, + ), + ] diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 00000000..5ddb01cb --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 5.1.6 on 2025-03-02 00:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CoreSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=255, unique=True)), + ('name', models.CharField(max_length=255)), + ('value', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='StreamProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('profile_name', models.CharField(help_text='Name of the stream profile', max_length=255)), + ('command', models.CharField(help_text="Command to execute (e.g., 'yt.sh', 'streamlink', or 'vlc')", max_length=255)), + ('parameters', models.TextField(help_text='Command-line parameters. Use {userAgent} and {streamUrl} as placeholders.')), + ('is_active', models.BooleanField(default=True, help_text='Whether this profile is active')), + ('user_agent', models.CharField(blank=True, help_text='Optional user agent to use. If not set, you can fall back to a default.', max_length=512, null=True)), + ], + ), + migrations.CreateModel( + name='UserAgent', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user_agent_name', models.CharField(help_text='The User-Agent name.', max_length=512, unique=True)), + ('user_agent', models.CharField(help_text='The complete User-Agent string sent by the client.', max_length=512, unique=True)), + ('description', models.CharField(blank=True, help_text='An optional description of the client or device type.', max_length=255)), + ('is_active', models.BooleanField(default=True, help_text='Whether this user agent is currently allowed/recognized.')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + ), + ] diff --git a/core/migrations/0002_preload_user_agents.py b/core/migrations/0002_preload_user_agents.py new file mode 100644 index 00000000..f7d495ab --- /dev/null +++ b/core/migrations/0002_preload_user_agents.py @@ -0,0 +1,36 @@ +# Generated by Django 5.1.6 on 2025-03-01 14:01 + +from django.db import migrations + +def preload_user_agent(apps, schema_editor): + UserAgent = apps.get_model("core", "UserAgent") + UserAgent.objects.create( + user_agent_name="TiviMate", + user_agent="TiviMate/5.16 (Android 12)", + description="", + is_active=True, + ) + + UserAgent.objects.create( + user_agent_name="VLC", + user_agent="VLC/3.0.21 LibVLC/3.0.21", + description="", + is_active=True, + ) + + UserAgent.objects.create( + user_agent_name="Chrome", + user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.3", + description="", + is_active=True, + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.RunPython(preload_user_agent), + ] diff --git a/core/migrations/0003_preload_stream_profiles.py b/core/migrations/0003_preload_stream_profiles.py new file mode 100644 index 00000000..6e7836c1 --- /dev/null +++ b/core/migrations/0003_preload_stream_profiles.py @@ -0,0 +1,31 @@ +# Generated by Django 5.1.6 on 2025-03-01 14:01 + +from django.db import migrations + +def preload_stream_profiles(apps, schema_editor): + StreamProfile = apps.get_model("core", "StreamProfile") + StreamProfile.objects.create( + profile_name="ffmpeg", + command="ffmpeg", + parameters="-i {streamUrl} -c:v copy -c:a copy -f mpegts pipe:1", + is_active=True, + user_agent="1", + ) + + StreamProfile.objects.create( + profile_name="streamlink", + command="streamlink", + parameters="{streamUrl} best --stdout", + is_active=True, + user_agent="1", + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_preload_user_agents'), + ] + + operations = [ + migrations.RunPython(preload_stream_profiles), + ] diff --git a/core/migrations/0004_preload_core_settings.py b/core/migrations/0004_preload_core_settings.py new file mode 100644 index 00000000..cc6f392a --- /dev/null +++ b/core/migrations/0004_preload_core_settings.py @@ -0,0 +1,28 @@ +# Generated by Django 5.1.6 on 2025-03-01 14:01 + +from django.db import migrations +from django.utils.text import slugify + +def preload_core_settings(apps, schema_editor): + CoreSettings = apps.get_model("core", "CoreSettings") + CoreSettings.objects.create( + key=slugify("Default Stream Profile"), + name="Default Stream Profile", + value=1, + ) + + CoreSettings.objects.create( + key=slugify("Default User-Agent"), + name="Default User-Agent", + value=1, + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_preload_stream_profiles'), + ] + + operations = [ + migrations.RunPython(preload_core_settings), + ]