Initial commit: igny8 project

This commit is contained in:
igny8
2025-11-09 10:27:02 +00:00
commit 60b8188111
27265 changed files with 4360521 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
# Generated by Django 5.2.7 on 2025-11-02 21:42
import django.contrib.auth.models
import django.contrib.auth.validators
import django.core.validators
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='Plan',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255, unique=True)),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('credits_per_month', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])),
('max_sites', models.IntegerField(default=1, help_text='Maximum number of sites allowed (1-10)', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(10)])),
('features', models.JSONField(default=dict, help_text='Plan features as JSON')),
('stripe_price_id', models.CharField(blank=True, max_length=255, null=True)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
options={
'db_table': 'igny8_plans',
'ordering': ['price'],
},
),
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')),
('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')),
('role', models.CharField(choices=[('owner', 'Owner'), ('admin', 'Admin'), ('editor', 'Editor'), ('viewer', 'Viewer'), ('system_bot', 'System Bot')], default='viewer', max_length=20)),
('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('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={
'db_table': 'igny8_users',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Tenant',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255, unique=True)),
('stripe_customer_id', models.CharField(blank=True, max_length=255, null=True)),
('credits', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])),
('status', models.CharField(choices=[('active', 'Active'), ('suspended', 'Suspended'), ('trial', 'Trial'), ('cancelled', 'Cancelled')], default='trial', max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='owned_tenants', to=settings.AUTH_USER_MODEL)),
('plan', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='tenants', to='igny8_core_auth.plan')),
],
options={
'db_table': 'igny8_tenants',
},
),
migrations.CreateModel(
name='Subscription',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stripe_subscription_id', models.CharField(max_length=255, unique=True)),
('status', models.CharField(choices=[('active', 'Active'), ('past_due', 'Past Due'), ('canceled', 'Canceled'), ('trialing', 'Trialing')], max_length=20)),
('current_period_start', models.DateTimeField()),
('current_period_end', models.DateTimeField()),
('cancel_at_period_end', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('tenant', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='subscription', to='igny8_core_auth.tenant')),
],
options={
'db_table': 'igny8_subscriptions',
},
),
migrations.CreateModel(
name='Site',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255)),
('domain', models.URLField(blank=True, help_text='Primary domain URL', null=True)),
('description', models.TextField(blank=True, null=True)),
('is_active', models.BooleanField(db_index=True, default=True)),
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('suspended', 'Suspended')], default='active', max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('wp_url', models.URLField(blank=True, help_text='WordPress site URL', null=True)),
('wp_username', models.CharField(blank=True, max_length=255, null=True)),
('wp_app_password', models.CharField(blank=True, max_length=255, null=True)),
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
],
options={
'db_table': 'igny8_sites',
},
),
migrations.CreateModel(
name='Sector',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('is_active', models.BooleanField(db_index=True, default=True)),
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive')], default='active', max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sectors', to='igny8_core_auth.site')),
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
],
options={
'db_table': 'igny8_sectors',
},
),
migrations.AddField(
model_name='user',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='users', to='igny8_core_auth.tenant'),
),
migrations.CreateModel(
name='SiteUserAccess',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('granted_at', models.DateTimeField(auto_now_add=True)),
('granted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='granted_site_accesses', to=settings.AUTH_USER_MODEL)),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_access', to='igny8_core_auth.site')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='site_access', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'igny8_site_user_access',
'indexes': [models.Index(fields=['user', 'site'], name='igny8_site__user_id_61951e_idx')],
'unique_together': {('user', 'site')},
},
),
migrations.AddIndex(
model_name='tenant',
index=models.Index(fields=['slug'], name='igny8_tenan_slug_f25e97_idx'),
),
migrations.AddIndex(
model_name='tenant',
index=models.Index(fields=['status'], name='igny8_tenan_status_5dc02a_idx'),
),
migrations.AddIndex(
model_name='subscription',
index=models.Index(fields=['status'], name='igny8_subsc_status_2fa897_idx'),
),
migrations.AddIndex(
model_name='site',
index=models.Index(fields=['tenant', 'is_active'], name='igny8_sites_tenant__e0f31d_idx'),
),
migrations.AddIndex(
model_name='site',
index=models.Index(fields=['tenant', 'status'], name='igny8_sites_tenant__a20275_idx'),
),
migrations.AlterUniqueTogether(
name='site',
unique_together={('tenant', 'slug')},
),
migrations.AddIndex(
model_name='sector',
index=models.Index(fields=['site', 'is_active'], name='igny8_secto_site_id_76b3c7_idx'),
),
migrations.AddIndex(
model_name='sector',
index=models.Index(fields=['tenant', 'site'], name='igny8_secto_tenant__af54ae_idx'),
),
migrations.AlterUniqueTogether(
name='sector',
unique_together={('site', 'slug')},
),
migrations.AddIndex(
model_name='user',
index=models.Index(fields=['tenant', 'role'], name='igny8_users_tenant__0ab02b_idx'),
),
migrations.AddIndex(
model_name='user',
index=models.Index(fields=['email'], name='igny8_users_email_fd61ff_idx'),
),
]

View File

@@ -0,0 +1,13 @@
# Generated by Django 5.2.7 on 2025-11-02 22:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0001_initial'),
]
operations = [
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-11-03 13:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0002_add_developer_role'),
]
operations = [
migrations.AlterField(
model_name='user',
name='role',
field=models.CharField(choices=[('developer', 'Developer / Super Admin'), ('owner', 'Owner'), ('admin', 'Admin'), ('editor', 'Editor'), ('viewer', 'Viewer'), ('system_bot', 'System Bot')], default='viewer', max_length=20),
),
]

View File

@@ -0,0 +1,75 @@
# Generated migration for Industry and IndustrySector models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0003_alter_user_role'),
]
operations = [
migrations.CreateModel(
name='Industry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('slug', models.SlugField(db_index=True, max_length=255, unique=True)),
('description', models.TextField(blank=True, null=True)),
('is_active', models.BooleanField(db_index=True, default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'igny8_industries',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='IndustrySector',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('slug', models.SlugField(db_index=True, max_length=255)),
('description', models.TextField(blank=True, null=True)),
('suggested_keywords', models.JSONField(default=list, help_text='List of suggested keywords for this sector template')),
('is_active', models.BooleanField(db_index=True, default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('industry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sectors', to='igny8_core_auth.industry')),
],
options={
'db_table': 'igny8_industry_sectors',
'ordering': ['industry', 'name'],
'unique_together': {('industry', 'slug')},
},
),
migrations.AddField(
model_name='sector',
name='industry_sector',
field=models.ForeignKey(blank=True, help_text='Reference to the industry sector template', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='site_sectors', to='igny8_core_auth.industrysector'),
),
migrations.AddIndex(
model_name='industry',
index=models.Index(fields=['slug'], name='igny8_indu_slug_idx'),
),
migrations.AddIndex(
model_name='industry',
index=models.Index(fields=['is_active'], name='igny8_indu_is_acti_idx'),
),
migrations.AddIndex(
model_name='industrysector',
index=models.Index(fields=['industry', 'is_active'], name='igny8_indu_industr_idx'),
),
migrations.AddIndex(
model_name='industrysector',
index=models.Index(fields=['slug'], name='igny8_indu_slug_1_idx'),
),
migrations.AddIndex(
model_name='sector',
index=models.Index(fields=['industry_sector'], name='igny8_sect_industr_idx'),
),
]

View File

@@ -0,0 +1,31 @@
# Migration to add industry field to Site model
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0004_add_industry_models'),
]
operations = [
migrations.AddField(
model_name='site',
name='industry',
field=models.ForeignKey(
blank=True,
help_text='Industry this site belongs to',
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='sites',
to='igny8_core_auth.industry'
),
),
migrations.AddIndex(
model_name='site',
index=models.Index(fields=['industry'], name='igny8_site_industr_idx'),
),
]

View File

@@ -0,0 +1,151 @@
"""Add extended plan configuration fields"""
from decimal import Decimal
from django.core.validators import MinValueValidator
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0006_add_industry_to_site'),
]
operations = [
migrations.AddField(
model_name='plan',
name='ai_cost_per_request',
field=models.JSONField(default=dict, help_text="Cost per request type (e.g., {'cluster': 2, 'idea': 3, 'content': 5, 'image': 1})"),
),
migrations.AddField(
model_name='plan',
name='allow_credit_topup',
field=models.BooleanField(default=True, help_text='Can user purchase more credits?'),
),
migrations.AddField(
model_name='plan',
name='billing_cycle',
field=models.CharField(choices=[('monthly', 'Monthly'), ('annual', 'Annual')], default='monthly', max_length=20),
),
migrations.AddField(
model_name='plan',
name='daily_ai_request_limit',
field=models.IntegerField(default=100, help_text='Global daily AI request cap', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='daily_ai_requests',
field=models.IntegerField(default=50, help_text='Total AI executions (content + idea + image) allowed per day', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='daily_cluster_limit',
field=models.IntegerField(default=10, help_text='Max clusters that can be created per day', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='daily_content_tasks',
field=models.IntegerField(default=10, help_text='Max number of content tasks (blogs) per day', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='daily_keyword_import_limit',
field=models.IntegerField(default=100, help_text='SeedKeywords import limit per day', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='extra_credit_price',
field=models.DecimalField(decimal_places=2, default=Decimal('0.01'), help_text='Price per additional credit', max_digits=10),
),
migrations.AddField(
model_name='plan',
name='image_model_choices',
field=models.JSONField(default=list, help_text="Allowed image models (e.g., ['dalle3', 'hidream'])"),
),
migrations.AddField(
model_name='plan',
name='included_credits',
field=models.IntegerField(default=0, help_text='Monthly credits included', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='max_author_profiles',
field=models.IntegerField(default=5, help_text='Limit for saved writing styles', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='max_clusters',
field=models.IntegerField(default=100, help_text='Total clusters allowed (global)', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='max_images_per_task',
field=models.IntegerField(default=4, help_text='Max images per content task', validators=[MinValueValidator(1)]),
),
migrations.AddField(
model_name='plan',
name='max_industries',
field=models.IntegerField(blank=True, default=None, help_text='Optional limit for industries/sectors', null=True, validators=[MinValueValidator(1)]),
),
migrations.AddField(
model_name='plan',
name='max_keywords',
field=models.IntegerField(default=1000, help_text='Total keywords allowed (global limit)', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='max_users',
field=models.IntegerField(default=1, help_text='Total users allowed per account', validators=[MinValueValidator(1)]),
),
migrations.AddField(
model_name='plan',
name='monthly_ai_credit_limit',
field=models.IntegerField(default=500, help_text='Unified credit ceiling per month (all AI functions)', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='monthly_cluster_ai_credits',
field=models.IntegerField(default=50, help_text='AI credits allocated for clustering', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='monthly_content_ai_credits',
field=models.IntegerField(default=200, help_text='AI credit pool for content generation', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='monthly_image_ai_credits',
field=models.IntegerField(default=100, help_text='AI credit pool for image generation', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='monthly_image_count',
field=models.IntegerField(default=100, help_text='Max images per month', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='monthly_word_count_limit',
field=models.IntegerField(default=50000, help_text='Monthly word limit (for generated content)', validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='auto_credit_topup_threshold',
field=models.IntegerField(blank=True, default=None, help_text='Auto top-up trigger point (optional)', null=True, validators=[MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='auto_credit_topup_amount',
field=models.IntegerField(blank=True, default=None, help_text='How many credits to auto-buy', null=True, validators=[MinValueValidator(1)]),
),
migrations.AddField(
model_name='plan',
name='stripe_product_id',
field=models.CharField(blank=True, help_text='For Stripe plan sync', max_length=255, null=True),
),
migrations.AlterField(
model_name='plan',
name='features',
field=models.JSONField(default=list, help_text="Plan features as JSON array (e.g., ['ai_writer', 'image_gen', 'auto_publish'])"),
),
]

View File

@@ -0,0 +1,108 @@
# Generated by Django 5.2.8 on 2025-11-07 10:06
import django.core.validators
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0007_expand_plan_limits'),
]
operations = [
migrations.CreateModel(
name='PasswordResetToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(db_index=True, max_length=255, unique=True)),
('expires_at', models.DateTimeField()),
('used', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
options={
'db_table': 'igny8_password_reset_tokens',
'ordering': ['-created_at'],
},
),
migrations.AlterModelOptions(
name='industry',
options={'ordering': ['name'], 'verbose_name': 'Industry', 'verbose_name_plural': 'Industries'},
),
migrations.AlterModelOptions(
name='industrysector',
options={'ordering': ['industry', 'name'], 'verbose_name': 'Industry Sector', 'verbose_name_plural': 'Industry Sectors'},
),
migrations.AlterModelOptions(
name='site',
options={'ordering': ['-created_at']},
),
migrations.AlterModelOptions(
name='siteuseraccess',
options={'verbose_name': 'Site User Access', 'verbose_name_plural': 'Site User Access'},
),
migrations.RenameIndex(
model_name='industry',
new_name='igny8_indus_slug_2f8769_idx',
old_name='igny8_indu_slug_idx',
),
migrations.RenameIndex(
model_name='industry',
new_name='igny8_indus_is_acti_146d41_idx',
old_name='igny8_indu_is_acti_idx',
),
migrations.RenameIndex(
model_name='industrysector',
new_name='igny8_indus_industr_00b524_idx',
old_name='igny8_indu_industr_idx',
),
migrations.RenameIndex(
model_name='industrysector',
new_name='igny8_indus_slug_101d63_idx',
old_name='igny8_indu_slug_1_idx',
),
migrations.RenameIndex(
model_name='sector',
new_name='igny8_secto_industr_1cf990_idx',
old_name='igny8_sect_industr_idx',
),
migrations.RenameIndex(
model_name='site',
new_name='igny8_sites_industr_66e004_idx',
old_name='igny8_site_industr_idx',
),
migrations.AlterField(
model_name='plan',
name='credits_per_month',
field=models.IntegerField(default=0, help_text='DEPRECATED: Use included_credits instead', validators=[django.core.validators.MinValueValidator(0)]),
),
migrations.AlterField(
model_name='plan',
name='extra_credit_price',
field=models.DecimalField(decimal_places=2, default=0.01, help_text='Price per additional credit', max_digits=10),
),
migrations.AlterField(
model_name='plan',
name='stripe_price_id',
field=models.CharField(blank=True, help_text='Monthly price ID for Stripe', max_length=255, null=True),
),
migrations.AddField(
model_name='passwordresettoken',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='password_reset_tokens', to=settings.AUTH_USER_MODEL),
),
migrations.AddIndex(
model_name='passwordresettoken',
index=models.Index(fields=['token'], name='igny8_passw_token_0eaf0c_idx'),
),
migrations.AddIndex(
model_name='passwordresettoken',
index=models.Index(fields=['user', 'used'], name='igny8_passw_user_id_320c02_idx'),
),
migrations.AddIndex(
model_name='passwordresettoken',
index=models.Index(fields=['expires_at'], name='igny8_passw_expires_c9aa03_idx'),
),
]

View File

@@ -0,0 +1,80 @@
from django.db import migrations
def forward_fix_admin_log_fk(apps, schema_editor):
if schema_editor.connection.vendor != "postgresql":
return
schema_editor.execute(
"""
ALTER TABLE django_admin_log
DROP CONSTRAINT IF EXISTS django_admin_log_user_id_c564eba6_fk_auth_user_id;
"""
)
schema_editor.execute(
"""
UPDATE django_admin_log
SET user_id = sub.new_user_id
FROM (
SELECT id AS new_user_id
FROM igny8_users
ORDER BY id
LIMIT 1
) AS sub
WHERE django_admin_log.user_id NOT IN (
SELECT id FROM igny8_users
);
"""
)
schema_editor.execute(
"""
ALTER TABLE django_admin_log
ADD CONSTRAINT django_admin_log_user_id_c564eba6_fk_igny8_users_id
FOREIGN KEY (user_id) REFERENCES igny8_users(id) DEFERRABLE INITIALLY DEFERRED;
"""
)
def reverse_fix_admin_log_fk(apps, schema_editor):
if schema_editor.connection.vendor != "postgresql":
return
schema_editor.execute(
"""
ALTER TABLE django_admin_log
DROP CONSTRAINT IF EXISTS django_admin_log_user_id_c564eba6_fk_igny8_users_id;
"""
)
schema_editor.execute(
"""
UPDATE django_admin_log
SET user_id = sub.old_user_id
FROM (
SELECT id AS old_user_id
FROM auth_user
ORDER BY id
LIMIT 1
) AS sub
WHERE django_admin_log.user_id NOT IN (
SELECT id FROM auth_user
);
"""
)
schema_editor.execute(
"""
ALTER TABLE django_admin_log
ADD CONSTRAINT django_admin_log_user_id_c564eba6_fk_auth_user_id
FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED;
"""
)
class Migration(migrations.Migration):
dependencies = [
("igny8_core_auth", "0008_passwordresettoken_alter_industry_options_and_more"),
]
operations = [
migrations.RunPython(forward_fix_admin_log_fk, reverse_fix_admin_log_fk),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.2.8 on 2025-11-07 11:34
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0009_fix_admin_log_user_fk'),
]
operations = [
migrations.CreateModel(
name='SeedKeyword',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('keyword', models.CharField(db_index=True, max_length=255)),
('volume', models.IntegerField(default=0, help_text='Search volume estimate')),
('difficulty', models.IntegerField(default=0, help_text='Keyword difficulty (0-100)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('intent', models.CharField(choices=[('informational', 'Informational'), ('navigational', 'Navigational'), ('commercial', 'Commercial'), ('transactional', 'Transactional')], default='informational', max_length=50)),
('is_active', models.BooleanField(db_index=True, default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('industry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seed_keywords', to='igny8_core_auth.industry')),
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seed_keywords', to='igny8_core_auth.industrysector')),
],
options={
'verbose_name': 'Seed Keyword',
'verbose_name_plural': 'Seed Keywords',
'db_table': 'igny8_seed_keywords',
'ordering': ['keyword'],
'indexes': [models.Index(fields=['keyword'], name='igny8_seed__keyword_efa089_idx'), models.Index(fields=['industry', 'sector'], name='igny8_seed__industr_c41841_idx'), models.Index(fields=['industry', 'sector', 'is_active'], name='igny8_seed__industr_da0030_idx'), models.Index(fields=['intent'], name='igny8_seed__intent_15020d_idx')],
'unique_together': {('keyword', 'industry', 'sector')},
},
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 5.2.7 on 2025-11-07 11:45
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0010_add_seed_keyword'),
]
operations = [
migrations.AddField(
model_name='plan',
name='daily_image_generation_limit',
field=models.IntegerField(default=25, help_text='Max images that can be generated per day', validators=[django.core.validators.MinValueValidator(0)]),
),
migrations.AddField(
model_name='plan',
name='max_content_ideas',
field=models.IntegerField(default=300, help_text='Total content ideas allowed (global limit)', validators=[django.core.validators.MinValueValidator(0)]),
),
migrations.AlterField(
model_name='plan',
name='max_sites',
field=models.IntegerField(default=1, help_text='Maximum number of sites allowed', validators=[django.core.validators.MinValueValidator(1)]),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2025-11-07 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0011_add_plan_fields_and_fix_constraints'),
]
operations = [
migrations.AlterField(
model_name='plan',
name='ai_cost_per_request',
field=models.JSONField(blank=True, default=dict, help_text="Cost per request type (e.g., {'cluster': 2, 'idea': 3, 'content': 5, 'image': 1})"),
),
migrations.AlterField(
model_name='plan',
name='features',
field=models.JSONField(blank=True, default=list, help_text="Plan features as JSON array (e.g., ['ai_writer', 'image_gen', 'auto_publish'])"),
),
migrations.AlterField(
model_name='plan',
name='image_model_choices',
field=models.JSONField(blank=True, default=list, help_text="Allowed image models (e.g., ['dalle3', 'hidream'])"),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.7 on 2025-11-07 12:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0012_allow_blank_json_fields'),
]
operations = [
migrations.RemoveField(
model_name='plan',
name='ai_cost_per_request',
),
]