5764118b8e
- Migration 034: creates app_config table with JSONB columns (render, storage, worker, notifications, billing); migrates existing system_settings values - backend/app/core/config_service.py: - Typed Pydantic models: RenderConfig, WorkerConfig, StorageConfig, etc. - AppConfig aggregate model - get_app_config(db) async + get_app_config_sync() for Celery tasks - update_render_config() / update_worker_config() for partial updates - system_settings table preserved for backward compat during Phase B migration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
93 lines
4.6 KiB
Python
93 lines
4.6 KiB
Python
"""Add app_config table (structured settings to replace key-value system_settings).
|
|
|
|
app_config stores settings grouped in JSONB columns:
|
|
- render: thumbnail_renderer, engine, samples, stl_quality, etc.
|
|
- storage: upload_dir, minio_url, max_upload_size_mb, etc.
|
|
- notifications: (reserved for Phase G)
|
|
- worker: concurrency, max_concurrent_renders, stall_timeout_minutes
|
|
- billing: (reserved for Phase L)
|
|
|
|
system_settings table is preserved for backward compatibility during migration.
|
|
Existing values are migrated into app_config.
|
|
|
|
Revision ID: 034
|
|
Revises: 033
|
|
Create Date: 2026-03-06
|
|
"""
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
|
|
revision = '034'
|
|
down_revision = '033'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade():
|
|
op.create_table(
|
|
'app_config',
|
|
sa.Column('id', UUID(as_uuid=True), primary_key=True,
|
|
server_default=sa.text('gen_random_uuid()')),
|
|
sa.Column('version', sa.Integer(), nullable=False, server_default='1'),
|
|
sa.Column('render', JSONB(), nullable=False, server_default='{}'),
|
|
sa.Column('storage', JSONB(), nullable=False, server_default='{}'),
|
|
sa.Column('notifications', JSONB(), nullable=False, server_default='{}'),
|
|
sa.Column('worker', JSONB(), nullable=False, server_default='{}'),
|
|
sa.Column('billing', JSONB(), nullable=False, server_default='{}'),
|
|
sa.Column('updated_at', sa.DateTime(), nullable=False,
|
|
server_default=sa.text('NOW()')),
|
|
)
|
|
|
|
# Migrate existing system_settings into app_config render + worker columns
|
|
op.execute("""
|
|
INSERT INTO app_config (render, worker, updated_at)
|
|
SELECT
|
|
jsonb_build_object(
|
|
'thumbnail_renderer', COALESCE(MAX(CASE WHEN key = 'thumbnail_renderer' THEN value END), 'blender'),
|
|
'blender_engine', COALESCE(MAX(CASE WHEN key = 'blender_engine' THEN value END), 'cycles'),
|
|
'blender_cycles_samples', COALESCE(MAX(CASE WHEN key = 'blender_cycles_samples' THEN value END), '256')::int,
|
|
'blender_eevee_samples', COALESCE(MAX(CASE WHEN key = 'blender_eevee_samples' THEN value END), '64')::int,
|
|
'thumbnail_format', COALESCE(MAX(CASE WHEN key = 'thumbnail_format' THEN value END), 'jpg'),
|
|
'stl_quality', COALESCE(MAX(CASE WHEN key = 'stl_quality' THEN value END), 'low'),
|
|
'blender_smooth_angle', COALESCE(MAX(CASE WHEN key = 'blender_smooth_angle' THEN value END), '30')::int,
|
|
'cycles_device', COALESCE(MAX(CASE WHEN key = 'cycles_device' THEN value END), 'auto'),
|
|
'render_backend', COALESCE(MAX(CASE WHEN key = 'render_backend' THEN value END), 'celery'),
|
|
'product_thumbnail_priority', COALESCE(MAX(CASE WHEN key = 'product_thumbnail_priority' THEN value END), '["latest_render","cad_thumbnail"]')
|
|
),
|
|
jsonb_build_object(
|
|
'concurrency', COALESCE(MAX(CASE WHEN key = 'celery_worker_concurrency' THEN value END), '8')::int,
|
|
'max_concurrent_renders', COALESCE(MAX(CASE WHEN key = 'blender_max_concurrent_renders' THEN value END), '3')::int,
|
|
'render_stall_timeout_minutes', COALESCE(MAX(CASE WHEN key = 'render_stall_timeout_minutes' THEN value END), '120')::int
|
|
),
|
|
NOW()
|
|
FROM system_settings
|
|
WHERE key IN (
|
|
'thumbnail_renderer', 'blender_engine', 'blender_cycles_samples',
|
|
'blender_eevee_samples', 'thumbnail_format', 'stl_quality',
|
|
'blender_smooth_angle', 'cycles_device', 'render_backend',
|
|
'product_thumbnail_priority', 'celery_worker_concurrency',
|
|
'blender_max_concurrent_renders', 'render_stall_timeout_minutes'
|
|
)
|
|
HAVING COUNT(*) > 0
|
|
""")
|
|
|
|
# Insert default row if no system_settings existed
|
|
op.execute("""
|
|
INSERT INTO app_config (render, worker, updated_at)
|
|
SELECT
|
|
'{"thumbnail_renderer": "blender", "blender_engine": "cycles",
|
|
"blender_cycles_samples": 256, "blender_eevee_samples": 64,
|
|
"thumbnail_format": "jpg", "stl_quality": "low",
|
|
"blender_smooth_angle": 30, "cycles_device": "auto",
|
|
"render_backend": "celery",
|
|
"product_thumbnail_priority": ["latest_render", "cad_thumbnail"]}'::jsonb,
|
|
'{"concurrency": 8, "max_concurrent_renders": 3, "render_stall_timeout_minutes": 120}'::jsonb,
|
|
NOW()
|
|
WHERE NOT EXISTS (SELECT 1 FROM app_config)
|
|
""")
|
|
|
|
|
|
def downgrade():
|
|
op.drop_table('app_config')
|