From acd18ff694a107f170b1143dbdbc1a807a569791 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Sat, 3 Jan 2026 01:35:18 +0100 Subject: [PATCH] chore(backend): standardize multiline formatting across modules Reformatted multiline function calls, object definitions, and queries for improved code readability and consistency. Adjusted imports and constraints where necessary. --- backend/app/alembic/env.py | 1 + .../alembic/versions/0001_initial_models.py | 634 +++++++++++------- .../versions/0002_add_performance_indexes.py | 9 +- .../versions/0004_add_syndarix_models.py | 12 +- backend/app/api/main.py | 4 +- backend/app/api/routes/agents.py | 21 +- backend/app/api/routes/events.py | 4 +- backend/app/api/routes/issues.py | 8 +- backend/app/api/routes/projects.py | 28 +- backend/app/api/routes/sprints.py | 12 +- backend/app/core/redis.py | 12 +- backend/app/crud/syndarix/agent_instance.py | 16 +- backend/app/crud/syndarix/agent_type.py | 20 +- backend/app/crud/syndarix/issue.py | 8 +- backend/app/crud/syndarix/project.py | 19 +- backend/app/crud/syndarix/sprint.py | 15 +- backend/app/models/syndarix/issue.py | 6 +- backend/app/models/syndarix/sprint.py | 12 +- backend/app/schemas/events.py | 4 +- backend/app/schemas/syndarix/issue.py | 4 +- backend/app/services/event_bus.py | 4 - backend/app/services/oauth_service.py | 12 +- backend/app/tasks/agent.py | 8 +- backend/app/tasks/git.py | 8 +- backend/app/tasks/sync.py | 8 +- backend/app/tasks/workflow.py | 8 +- 26 files changed, 540 insertions(+), 357 deletions(-) diff --git a/backend/app/alembic/env.py b/backend/app/alembic/env.py index 764852a..524f29a 100644 --- a/backend/app/alembic/env.py +++ b/backend/app/alembic/env.py @@ -40,6 +40,7 @@ def include_object(object, name, type_, reflected, compare_to): return False return True + # Interpret the config file for Python logging. # This line sets up loggers basically. if config.config_file_name is not None: diff --git a/backend/app/alembic/versions/0001_initial_models.py b/backend/app/alembic/versions/0001_initial_models.py index d93341c..ccfd14a 100644 --- a/backend/app/alembic/versions/0001_initial_models.py +++ b/backend/app/alembic/versions/0001_initial_models.py @@ -5,6 +5,7 @@ Revises: Create Date: 2025-11-27 09:08:09.464506 """ + from collections.abc import Sequence import sqlalchemy as sa @@ -12,7 +13,7 @@ from alembic import op from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. -revision: str = '0001' +revision: str = "0001" down_revision: str | None = None branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None @@ -20,243 +21,426 @@ depends_on: str | Sequence[str] | None = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('oauth_states', - sa.Column('state', sa.String(length=255), nullable=False), - sa.Column('code_verifier', sa.String(length=128), nullable=True), - sa.Column('nonce', sa.String(length=255), nullable=True), - sa.Column('provider', sa.String(length=50), nullable=False), - sa.Column('redirect_uri', sa.String(length=500), nullable=True), - sa.Column('user_id', sa.UUID(), nullable=True), - sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "oauth_states", + sa.Column("state", sa.String(length=255), nullable=False), + sa.Column("code_verifier", sa.String(length=128), nullable=True), + sa.Column("nonce", sa.String(length=255), nullable=True), + sa.Column("provider", sa.String(length=50), nullable=False), + sa.Column("redirect_uri", sa.String(length=500), nullable=True), + sa.Column("user_id", sa.UUID(), nullable=True), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.PrimaryKeyConstraint("id"), ) - op.create_index(op.f('ix_oauth_states_state'), 'oauth_states', ['state'], unique=True) - op.create_table('organizations', - sa.Column('name', sa.String(length=255), nullable=False), - sa.Column('slug', sa.String(length=255), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('settings', postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_index( + op.f("ix_oauth_states_state"), "oauth_states", ["state"], unique=True ) - op.create_index(op.f('ix_organizations_is_active'), 'organizations', ['is_active'], unique=False) - op.create_index(op.f('ix_organizations_name'), 'organizations', ['name'], unique=False) - op.create_index('ix_organizations_name_active', 'organizations', ['name', 'is_active'], unique=False) - op.create_index(op.f('ix_organizations_slug'), 'organizations', ['slug'], unique=True) - op.create_index('ix_organizations_slug_active', 'organizations', ['slug', 'is_active'], unique=False) - op.create_table('users', - sa.Column('email', sa.String(length=255), nullable=False), - sa.Column('password_hash', sa.String(length=255), nullable=True), - sa.Column('first_name', sa.String(length=100), nullable=False), - sa.Column('last_name', sa.String(length=100), nullable=True), - sa.Column('phone_number', sa.String(length=20), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('is_superuser', sa.Boolean(), nullable=False), - sa.Column('preferences', postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column('locale', sa.String(length=10), nullable=True), - sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "organizations", + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("slug", sa.String(length=255), nullable=False), + sa.Column("description", sa.Text(), nullable=True), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("settings", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.PrimaryKeyConstraint("id"), ) - op.create_index(op.f('ix_users_deleted_at'), 'users', ['deleted_at'], unique=False) - op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) - op.create_index(op.f('ix_users_is_active'), 'users', ['is_active'], unique=False) - op.create_index(op.f('ix_users_is_superuser'), 'users', ['is_superuser'], unique=False) - op.create_index(op.f('ix_users_locale'), 'users', ['locale'], unique=False) - op.create_table('oauth_accounts', - sa.Column('user_id', sa.UUID(), nullable=False), - sa.Column('provider', sa.String(length=50), nullable=False), - sa.Column('provider_user_id', sa.String(length=255), nullable=False), - sa.Column('provider_email', sa.String(length=255), nullable=True), - sa.Column('access_token_encrypted', sa.String(length=2048), nullable=True), - sa.Column('refresh_token_encrypted', sa.String(length=2048), nullable=True), - sa.Column('token_expires_at', sa.DateTime(timezone=True), nullable=True), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('provider', 'provider_user_id', name='uq_oauth_provider_user') + op.create_index( + op.f("ix_organizations_is_active"), "organizations", ["is_active"], unique=False ) - op.create_index(op.f('ix_oauth_accounts_provider'), 'oauth_accounts', ['provider'], unique=False) - op.create_index(op.f('ix_oauth_accounts_provider_email'), 'oauth_accounts', ['provider_email'], unique=False) - op.create_index(op.f('ix_oauth_accounts_user_id'), 'oauth_accounts', ['user_id'], unique=False) - op.create_index('ix_oauth_accounts_user_provider', 'oauth_accounts', ['user_id', 'provider'], unique=False) - op.create_table('oauth_clients', - sa.Column('client_id', sa.String(length=64), nullable=False), - sa.Column('client_secret_hash', sa.String(length=255), nullable=True), - sa.Column('client_name', sa.String(length=255), nullable=False), - sa.Column('client_description', sa.String(length=1000), nullable=True), - sa.Column('client_type', sa.String(length=20), nullable=False), - sa.Column('redirect_uris', postgresql.JSONB(astext_type=sa.Text()), nullable=False), - sa.Column('allowed_scopes', postgresql.JSONB(astext_type=sa.Text()), nullable=False), - sa.Column('access_token_lifetime', sa.String(length=10), nullable=False), - sa.Column('refresh_token_lifetime', sa.String(length=10), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('owner_user_id', sa.UUID(), nullable=True), - sa.Column('mcp_server_url', sa.String(length=2048), nullable=True), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['owner_user_id'], ['users.id'], ondelete='SET NULL'), - sa.PrimaryKeyConstraint('id') + op.create_index( + op.f("ix_organizations_name"), "organizations", ["name"], unique=False ) - op.create_index(op.f('ix_oauth_clients_client_id'), 'oauth_clients', ['client_id'], unique=True) - op.create_index(op.f('ix_oauth_clients_is_active'), 'oauth_clients', ['is_active'], unique=False) - op.create_table('user_organizations', - sa.Column('user_id', sa.UUID(), nullable=False), - sa.Column('organization_id', sa.UUID(), nullable=False), - sa.Column('role', sa.Enum('OWNER', 'ADMIN', 'MEMBER', 'GUEST', name='organizationrole'), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('custom_permissions', sa.String(length=500), nullable=True), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('user_id', 'organization_id') + op.create_index( + "ix_organizations_name_active", + "organizations", + ["name", "is_active"], + unique=False, ) - op.create_index('ix_user_org_org_active', 'user_organizations', ['organization_id', 'is_active'], unique=False) - op.create_index('ix_user_org_role', 'user_organizations', ['role'], unique=False) - op.create_index('ix_user_org_user_active', 'user_organizations', ['user_id', 'is_active'], unique=False) - op.create_index(op.f('ix_user_organizations_is_active'), 'user_organizations', ['is_active'], unique=False) - op.create_table('user_sessions', - sa.Column('user_id', sa.UUID(), nullable=False), - sa.Column('refresh_token_jti', sa.String(length=255), nullable=False), - sa.Column('device_name', sa.String(length=255), nullable=True), - sa.Column('device_id', sa.String(length=255), nullable=True), - sa.Column('ip_address', sa.String(length=45), nullable=True), - sa.Column('user_agent', sa.String(length=500), nullable=True), - sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('location_city', sa.String(length=100), nullable=True), - sa.Column('location_country', sa.String(length=100), nullable=True), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') + op.create_index( + op.f("ix_organizations_slug"), "organizations", ["slug"], unique=True ) - op.create_index(op.f('ix_user_sessions_is_active'), 'user_sessions', ['is_active'], unique=False) - op.create_index('ix_user_sessions_jti_active', 'user_sessions', ['refresh_token_jti', 'is_active'], unique=False) - op.create_index(op.f('ix_user_sessions_refresh_token_jti'), 'user_sessions', ['refresh_token_jti'], unique=True) - op.create_index('ix_user_sessions_user_active', 'user_sessions', ['user_id', 'is_active'], unique=False) - op.create_index(op.f('ix_user_sessions_user_id'), 'user_sessions', ['user_id'], unique=False) - op.create_table('oauth_authorization_codes', - sa.Column('code', sa.String(length=128), nullable=False), - sa.Column('client_id', sa.String(length=64), nullable=False), - sa.Column('user_id', sa.UUID(), nullable=False), - sa.Column('redirect_uri', sa.String(length=2048), nullable=False), - sa.Column('scope', sa.String(length=1000), nullable=False), - sa.Column('code_challenge', sa.String(length=128), nullable=True), - sa.Column('code_challenge_method', sa.String(length=10), nullable=True), - sa.Column('state', sa.String(length=256), nullable=True), - sa.Column('nonce', sa.String(length=256), nullable=True), - sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('used', sa.Boolean(), nullable=False), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['client_id'], ['oauth_clients.client_id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') + op.create_index( + "ix_organizations_slug_active", + "organizations", + ["slug", "is_active"], + unique=False, ) - op.create_index('ix_oauth_authorization_codes_client_user', 'oauth_authorization_codes', ['client_id', 'user_id'], unique=False) - op.create_index(op.f('ix_oauth_authorization_codes_code'), 'oauth_authorization_codes', ['code'], unique=True) - op.create_index('ix_oauth_authorization_codes_expires_at', 'oauth_authorization_codes', ['expires_at'], unique=False) - op.create_table('oauth_consents', - sa.Column('user_id', sa.UUID(), nullable=False), - sa.Column('client_id', sa.String(length=64), nullable=False), - sa.Column('granted_scopes', sa.String(length=1000), nullable=False), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['client_id'], ['oauth_clients.client_id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') + op.create_table( + "users", + sa.Column("email", sa.String(length=255), nullable=False), + sa.Column("password_hash", sa.String(length=255), nullable=True), + sa.Column("first_name", sa.String(length=100), nullable=False), + sa.Column("last_name", sa.String(length=100), nullable=True), + sa.Column("phone_number", sa.String(length=20), nullable=True), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("is_superuser", sa.Boolean(), nullable=False), + sa.Column( + "preferences", postgresql.JSONB(astext_type=sa.Text()), nullable=True + ), + sa.Column("locale", sa.String(length=10), nullable=True), + sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.PrimaryKeyConstraint("id"), ) - op.create_index('ix_oauth_consents_user_client', 'oauth_consents', ['user_id', 'client_id'], unique=True) - op.create_table('oauth_provider_refresh_tokens', - sa.Column('token_hash', sa.String(length=64), nullable=False), - sa.Column('jti', sa.String(length=64), nullable=False), - sa.Column('client_id', sa.String(length=64), nullable=False), - sa.Column('user_id', sa.UUID(), nullable=False), - sa.Column('scope', sa.String(length=1000), nullable=False), - sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('revoked', sa.Boolean(), nullable=False), - sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True), - sa.Column('device_info', sa.String(length=500), nullable=True), - sa.Column('ip_address', sa.String(length=45), nullable=True), - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['client_id'], ['oauth_clients.client_id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') + op.create_index(op.f("ix_users_deleted_at"), "users", ["deleted_at"], unique=False) + op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True) + op.create_index(op.f("ix_users_is_active"), "users", ["is_active"], unique=False) + op.create_index( + op.f("ix_users_is_superuser"), "users", ["is_superuser"], unique=False + ) + op.create_index(op.f("ix_users_locale"), "users", ["locale"], unique=False) + op.create_table( + "oauth_accounts", + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("provider", sa.String(length=50), nullable=False), + sa.Column("provider_user_id", sa.String(length=255), nullable=False), + sa.Column("provider_email", sa.String(length=255), nullable=True), + sa.Column("access_token_encrypted", sa.String(length=2048), nullable=True), + sa.Column("refresh_token_encrypted", sa.String(length=2048), nullable=True), + sa.Column("token_expires_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint( + "provider", "provider_user_id", name="uq_oauth_provider_user" + ), + ) + op.create_index( + op.f("ix_oauth_accounts_provider"), "oauth_accounts", ["provider"], unique=False + ) + op.create_index( + op.f("ix_oauth_accounts_provider_email"), + "oauth_accounts", + ["provider_email"], + unique=False, + ) + op.create_index( + op.f("ix_oauth_accounts_user_id"), "oauth_accounts", ["user_id"], unique=False + ) + op.create_index( + "ix_oauth_accounts_user_provider", + "oauth_accounts", + ["user_id", "provider"], + unique=False, + ) + op.create_table( + "oauth_clients", + sa.Column("client_id", sa.String(length=64), nullable=False), + sa.Column("client_secret_hash", sa.String(length=255), nullable=True), + sa.Column("client_name", sa.String(length=255), nullable=False), + sa.Column("client_description", sa.String(length=1000), nullable=True), + sa.Column("client_type", sa.String(length=20), nullable=False), + sa.Column( + "redirect_uris", postgresql.JSONB(astext_type=sa.Text()), nullable=False + ), + sa.Column( + "allowed_scopes", postgresql.JSONB(astext_type=sa.Text()), nullable=False + ), + sa.Column("access_token_lifetime", sa.String(length=10), nullable=False), + sa.Column("refresh_token_lifetime", sa.String(length=10), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("owner_user_id", sa.UUID(), nullable=True), + sa.Column("mcp_server_url", sa.String(length=2048), nullable=True), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint(["owner_user_id"], ["users.id"], ondelete="SET NULL"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_oauth_clients_client_id"), "oauth_clients", ["client_id"], unique=True + ) + op.create_index( + op.f("ix_oauth_clients_is_active"), "oauth_clients", ["is_active"], unique=False + ) + op.create_table( + "user_organizations", + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("organization_id", sa.UUID(), nullable=False), + sa.Column( + "role", + sa.Enum("OWNER", "ADMIN", "MEMBER", "GUEST", name="organizationrole"), + nullable=False, + ), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("custom_permissions", sa.String(length=500), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["organization_id"], ["organizations.id"], ondelete="CASCADE" + ), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("user_id", "organization_id"), + ) + op.create_index( + "ix_user_org_org_active", + "user_organizations", + ["organization_id", "is_active"], + unique=False, + ) + op.create_index("ix_user_org_role", "user_organizations", ["role"], unique=False) + op.create_index( + "ix_user_org_user_active", + "user_organizations", + ["user_id", "is_active"], + unique=False, + ) + op.create_index( + op.f("ix_user_organizations_is_active"), + "user_organizations", + ["is_active"], + unique=False, + ) + op.create_table( + "user_sessions", + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("refresh_token_jti", sa.String(length=255), nullable=False), + sa.Column("device_name", sa.String(length=255), nullable=True), + sa.Column("device_id", sa.String(length=255), nullable=True), + sa.Column("ip_address", sa.String(length=45), nullable=True), + sa.Column("user_agent", sa.String(length=500), nullable=True), + sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("location_city", sa.String(length=100), nullable=True), + sa.Column("location_country", sa.String(length=100), nullable=True), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_user_sessions_is_active"), "user_sessions", ["is_active"], unique=False + ) + op.create_index( + "ix_user_sessions_jti_active", + "user_sessions", + ["refresh_token_jti", "is_active"], + unique=False, + ) + op.create_index( + op.f("ix_user_sessions_refresh_token_jti"), + "user_sessions", + ["refresh_token_jti"], + unique=True, + ) + op.create_index( + "ix_user_sessions_user_active", + "user_sessions", + ["user_id", "is_active"], + unique=False, + ) + op.create_index( + op.f("ix_user_sessions_user_id"), "user_sessions", ["user_id"], unique=False + ) + op.create_table( + "oauth_authorization_codes", + sa.Column("code", sa.String(length=128), nullable=False), + sa.Column("client_id", sa.String(length=64), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("redirect_uri", sa.String(length=2048), nullable=False), + sa.Column("scope", sa.String(length=1000), nullable=False), + sa.Column("code_challenge", sa.String(length=128), nullable=True), + sa.Column("code_challenge_method", sa.String(length=10), nullable=True), + sa.Column("state", sa.String(length=256), nullable=True), + sa.Column("nonce", sa.String(length=256), nullable=True), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("used", sa.Boolean(), nullable=False), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["client_id"], ["oauth_clients.client_id"], ondelete="CASCADE" + ), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "ix_oauth_authorization_codes_client_user", + "oauth_authorization_codes", + ["client_id", "user_id"], + unique=False, + ) + op.create_index( + op.f("ix_oauth_authorization_codes_code"), + "oauth_authorization_codes", + ["code"], + unique=True, + ) + op.create_index( + "ix_oauth_authorization_codes_expires_at", + "oauth_authorization_codes", + ["expires_at"], + unique=False, + ) + op.create_table( + "oauth_consents", + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("client_id", sa.String(length=64), nullable=False), + sa.Column("granted_scopes", sa.String(length=1000), nullable=False), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["client_id"], ["oauth_clients.client_id"], ondelete="CASCADE" + ), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "ix_oauth_consents_user_client", + "oauth_consents", + ["user_id", "client_id"], + unique=True, + ) + op.create_table( + "oauth_provider_refresh_tokens", + sa.Column("token_hash", sa.String(length=64), nullable=False), + sa.Column("jti", sa.String(length=64), nullable=False), + sa.Column("client_id", sa.String(length=64), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("scope", sa.String(length=1000), nullable=False), + sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("revoked", sa.Boolean(), nullable=False), + sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("device_info", sa.String(length=500), nullable=True), + sa.Column("ip_address", sa.String(length=45), nullable=True), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint( + ["client_id"], ["oauth_clients.client_id"], ondelete="CASCADE" + ), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "ix_oauth_provider_refresh_tokens_client_user", + "oauth_provider_refresh_tokens", + ["client_id", "user_id"], + unique=False, + ) + op.create_index( + "ix_oauth_provider_refresh_tokens_expires_at", + "oauth_provider_refresh_tokens", + ["expires_at"], + unique=False, + ) + op.create_index( + op.f("ix_oauth_provider_refresh_tokens_jti"), + "oauth_provider_refresh_tokens", + ["jti"], + unique=True, + ) + op.create_index( + op.f("ix_oauth_provider_refresh_tokens_revoked"), + "oauth_provider_refresh_tokens", + ["revoked"], + unique=False, + ) + op.create_index( + op.f("ix_oauth_provider_refresh_tokens_token_hash"), + "oauth_provider_refresh_tokens", + ["token_hash"], + unique=True, + ) + op.create_index( + "ix_oauth_provider_refresh_tokens_user_revoked", + "oauth_provider_refresh_tokens", + ["user_id", "revoked"], + unique=False, ) - op.create_index('ix_oauth_provider_refresh_tokens_client_user', 'oauth_provider_refresh_tokens', ['client_id', 'user_id'], unique=False) - op.create_index('ix_oauth_provider_refresh_tokens_expires_at', 'oauth_provider_refresh_tokens', ['expires_at'], unique=False) - op.create_index(op.f('ix_oauth_provider_refresh_tokens_jti'), 'oauth_provider_refresh_tokens', ['jti'], unique=True) - op.create_index(op.f('ix_oauth_provider_refresh_tokens_revoked'), 'oauth_provider_refresh_tokens', ['revoked'], unique=False) - op.create_index(op.f('ix_oauth_provider_refresh_tokens_token_hash'), 'oauth_provider_refresh_tokens', ['token_hash'], unique=True) - op.create_index('ix_oauth_provider_refresh_tokens_user_revoked', 'oauth_provider_refresh_tokens', ['user_id', 'revoked'], unique=False) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_index('ix_oauth_provider_refresh_tokens_user_revoked', table_name='oauth_provider_refresh_tokens') - op.drop_index(op.f('ix_oauth_provider_refresh_tokens_token_hash'), table_name='oauth_provider_refresh_tokens') - op.drop_index(op.f('ix_oauth_provider_refresh_tokens_revoked'), table_name='oauth_provider_refresh_tokens') - op.drop_index(op.f('ix_oauth_provider_refresh_tokens_jti'), table_name='oauth_provider_refresh_tokens') - op.drop_index('ix_oauth_provider_refresh_tokens_expires_at', table_name='oauth_provider_refresh_tokens') - op.drop_index('ix_oauth_provider_refresh_tokens_client_user', table_name='oauth_provider_refresh_tokens') - op.drop_table('oauth_provider_refresh_tokens') - op.drop_index('ix_oauth_consents_user_client', table_name='oauth_consents') - op.drop_table('oauth_consents') - op.drop_index('ix_oauth_authorization_codes_expires_at', table_name='oauth_authorization_codes') - op.drop_index(op.f('ix_oauth_authorization_codes_code'), table_name='oauth_authorization_codes') - op.drop_index('ix_oauth_authorization_codes_client_user', table_name='oauth_authorization_codes') - op.drop_table('oauth_authorization_codes') - op.drop_index(op.f('ix_user_sessions_user_id'), table_name='user_sessions') - op.drop_index('ix_user_sessions_user_active', table_name='user_sessions') - op.drop_index(op.f('ix_user_sessions_refresh_token_jti'), table_name='user_sessions') - op.drop_index('ix_user_sessions_jti_active', table_name='user_sessions') - op.drop_index(op.f('ix_user_sessions_is_active'), table_name='user_sessions') - op.drop_table('user_sessions') - op.drop_index(op.f('ix_user_organizations_is_active'), table_name='user_organizations') - op.drop_index('ix_user_org_user_active', table_name='user_organizations') - op.drop_index('ix_user_org_role', table_name='user_organizations') - op.drop_index('ix_user_org_org_active', table_name='user_organizations') - op.drop_table('user_organizations') - op.drop_index(op.f('ix_oauth_clients_is_active'), table_name='oauth_clients') - op.drop_index(op.f('ix_oauth_clients_client_id'), table_name='oauth_clients') - op.drop_table('oauth_clients') - op.drop_index('ix_oauth_accounts_user_provider', table_name='oauth_accounts') - op.drop_index(op.f('ix_oauth_accounts_user_id'), table_name='oauth_accounts') - op.drop_index(op.f('ix_oauth_accounts_provider_email'), table_name='oauth_accounts') - op.drop_index(op.f('ix_oauth_accounts_provider'), table_name='oauth_accounts') - op.drop_table('oauth_accounts') - op.drop_index(op.f('ix_users_locale'), table_name='users') - op.drop_index(op.f('ix_users_is_superuser'), table_name='users') - op.drop_index(op.f('ix_users_is_active'), table_name='users') - op.drop_index(op.f('ix_users_email'), table_name='users') - op.drop_index(op.f('ix_users_deleted_at'), table_name='users') - op.drop_table('users') - op.drop_index('ix_organizations_slug_active', table_name='organizations') - op.drop_index(op.f('ix_organizations_slug'), table_name='organizations') - op.drop_index('ix_organizations_name_active', table_name='organizations') - op.drop_index(op.f('ix_organizations_name'), table_name='organizations') - op.drop_index(op.f('ix_organizations_is_active'), table_name='organizations') - op.drop_table('organizations') - op.drop_index(op.f('ix_oauth_states_state'), table_name='oauth_states') - op.drop_table('oauth_states') + op.drop_index( + "ix_oauth_provider_refresh_tokens_user_revoked", + table_name="oauth_provider_refresh_tokens", + ) + op.drop_index( + op.f("ix_oauth_provider_refresh_tokens_token_hash"), + table_name="oauth_provider_refresh_tokens", + ) + op.drop_index( + op.f("ix_oauth_provider_refresh_tokens_revoked"), + table_name="oauth_provider_refresh_tokens", + ) + op.drop_index( + op.f("ix_oauth_provider_refresh_tokens_jti"), + table_name="oauth_provider_refresh_tokens", + ) + op.drop_index( + "ix_oauth_provider_refresh_tokens_expires_at", + table_name="oauth_provider_refresh_tokens", + ) + op.drop_index( + "ix_oauth_provider_refresh_tokens_client_user", + table_name="oauth_provider_refresh_tokens", + ) + op.drop_table("oauth_provider_refresh_tokens") + op.drop_index("ix_oauth_consents_user_client", table_name="oauth_consents") + op.drop_table("oauth_consents") + op.drop_index( + "ix_oauth_authorization_codes_expires_at", + table_name="oauth_authorization_codes", + ) + op.drop_index( + op.f("ix_oauth_authorization_codes_code"), + table_name="oauth_authorization_codes", + ) + op.drop_index( + "ix_oauth_authorization_codes_client_user", + table_name="oauth_authorization_codes", + ) + op.drop_table("oauth_authorization_codes") + op.drop_index(op.f("ix_user_sessions_user_id"), table_name="user_sessions") + op.drop_index("ix_user_sessions_user_active", table_name="user_sessions") + op.drop_index( + op.f("ix_user_sessions_refresh_token_jti"), table_name="user_sessions" + ) + op.drop_index("ix_user_sessions_jti_active", table_name="user_sessions") + op.drop_index(op.f("ix_user_sessions_is_active"), table_name="user_sessions") + op.drop_table("user_sessions") + op.drop_index( + op.f("ix_user_organizations_is_active"), table_name="user_organizations" + ) + op.drop_index("ix_user_org_user_active", table_name="user_organizations") + op.drop_index("ix_user_org_role", table_name="user_organizations") + op.drop_index("ix_user_org_org_active", table_name="user_organizations") + op.drop_table("user_organizations") + op.drop_index(op.f("ix_oauth_clients_is_active"), table_name="oauth_clients") + op.drop_index(op.f("ix_oauth_clients_client_id"), table_name="oauth_clients") + op.drop_table("oauth_clients") + op.drop_index("ix_oauth_accounts_user_provider", table_name="oauth_accounts") + op.drop_index(op.f("ix_oauth_accounts_user_id"), table_name="oauth_accounts") + op.drop_index(op.f("ix_oauth_accounts_provider_email"), table_name="oauth_accounts") + op.drop_index(op.f("ix_oauth_accounts_provider"), table_name="oauth_accounts") + op.drop_table("oauth_accounts") + op.drop_index(op.f("ix_users_locale"), table_name="users") + op.drop_index(op.f("ix_users_is_superuser"), table_name="users") + op.drop_index(op.f("ix_users_is_active"), table_name="users") + op.drop_index(op.f("ix_users_email"), table_name="users") + op.drop_index(op.f("ix_users_deleted_at"), table_name="users") + op.drop_table("users") + op.drop_index("ix_organizations_slug_active", table_name="organizations") + op.drop_index(op.f("ix_organizations_slug"), table_name="organizations") + op.drop_index("ix_organizations_name_active", table_name="organizations") + op.drop_index(op.f("ix_organizations_name"), table_name="organizations") + op.drop_index(op.f("ix_organizations_is_active"), table_name="organizations") + op.drop_table("organizations") + op.drop_index(op.f("ix_oauth_states_state"), table_name="oauth_states") + op.drop_table("oauth_states") # ### end Alembic commands ### diff --git a/backend/app/alembic/versions/0002_add_performance_indexes.py b/backend/app/alembic/versions/0002_add_performance_indexes.py index 0754cc5..1c7987f 100644 --- a/backend/app/alembic/versions/0002_add_performance_indexes.py +++ b/backend/app/alembic/versions/0002_add_performance_indexes.py @@ -114,8 +114,13 @@ def upgrade() -> None: def downgrade() -> None: # Drop indexes in reverse order - op.drop_index("ix_perf_oauth_auth_codes_expires", table_name="oauth_authorization_codes") - op.drop_index("ix_perf_oauth_refresh_tokens_expires", table_name="oauth_provider_refresh_tokens") + op.drop_index( + "ix_perf_oauth_auth_codes_expires", table_name="oauth_authorization_codes" + ) + op.drop_index( + "ix_perf_oauth_refresh_tokens_expires", + table_name="oauth_provider_refresh_tokens", + ) op.drop_index("ix_perf_user_sessions_expires", table_name="user_sessions") op.drop_index("ix_perf_organizations_slug_lower", table_name="organizations") op.drop_index("ix_perf_users_active", table_name="users") diff --git a/backend/app/alembic/versions/0004_add_syndarix_models.py b/backend/app/alembic/versions/0004_add_syndarix_models.py index 2fb0ff9..bb3d38a 100644 --- a/backend/app/alembic/versions/0004_add_syndarix_models.py +++ b/backend/app/alembic/versions/0004_add_syndarix_models.py @@ -443,9 +443,7 @@ def upgrade() -> None: ), sa.PrimaryKeyConstraint("id"), sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"), - sa.ForeignKeyConstraint( - ["parent_id"], ["issues.id"], ondelete="CASCADE" - ), + sa.ForeignKeyConstraint(["parent_id"], ["issues.id"], ondelete="CASCADE"), sa.ForeignKeyConstraint(["sprint_id"], ["sprints.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint( ["assigned_agent_id"], ["agent_instances.id"], ondelete="SET NULL" @@ -462,7 +460,9 @@ def upgrade() -> None: op.create_index("ix_issues_human_assignee", "issues", ["human_assignee"]) op.create_index("ix_issues_sprint_id", "issues", ["sprint_id"]) op.create_index("ix_issues_due_date", "issues", ["due_date"]) - op.create_index("ix_issues_external_tracker_type", "issues", ["external_tracker_type"]) + op.create_index( + "ix_issues_external_tracker_type", "issues", ["external_tracker_type"] + ) op.create_index("ix_issues_sync_status", "issues", ["sync_status"]) op.create_index("ix_issues_closed_at", "issues", ["closed_at"]) # Composite indexes @@ -470,7 +470,9 @@ def upgrade() -> None: op.create_index("ix_issues_project_priority", "issues", ["project_id", "priority"]) op.create_index("ix_issues_project_sprint", "issues", ["project_id", "sprint_id"]) op.create_index("ix_issues_project_type", "issues", ["project_id", "type"]) - op.create_index("ix_issues_project_agent", "issues", ["project_id", "assigned_agent_id"]) + op.create_index( + "ix_issues_project_agent", "issues", ["project_id", "assigned_agent_id"] + ) op.create_index( "ix_issues_project_status_priority", "issues", diff --git a/backend/app/api/main.py b/backend/app/api/main.py index 36f9776..c083321 100644 --- a/backend/app/api/main.py +++ b/backend/app/api/main.py @@ -32,9 +32,7 @@ api_router.include_router( api_router.include_router(events.router, tags=["Events"]) # Syndarix domain routers -api_router.include_router( - projects.router, prefix="/projects", tags=["Projects"] -) +api_router.include_router(projects.router, prefix="/projects", tags=["Projects"]) api_router.include_router( agent_types.router, prefix="/agent-types", tags=["Agent Types"] ) diff --git a/backend/app/api/routes/agents.py b/backend/app/api/routes/agents.py index 7ed9ac1..781ea0c 100644 --- a/backend/app/api/routes/agents.py +++ b/backend/app/api/routes/agents.py @@ -57,8 +57,18 @@ RATE_MULTIPLIER = 100 if IS_TEST else 1 # Valid status transitions for agent lifecycle management VALID_STATUS_TRANSITIONS: dict[AgentStatus, set[AgentStatus]] = { AgentStatus.IDLE: {AgentStatus.WORKING, AgentStatus.PAUSED, AgentStatus.TERMINATED}, - AgentStatus.WORKING: {AgentStatus.IDLE, AgentStatus.WAITING, AgentStatus.PAUSED, AgentStatus.TERMINATED}, - AgentStatus.WAITING: {AgentStatus.IDLE, AgentStatus.WORKING, AgentStatus.PAUSED, AgentStatus.TERMINATED}, + AgentStatus.WORKING: { + AgentStatus.IDLE, + AgentStatus.WAITING, + AgentStatus.PAUSED, + AgentStatus.TERMINATED, + }, + AgentStatus.WAITING: { + AgentStatus.IDLE, + AgentStatus.WORKING, + AgentStatus.PAUSED, + AgentStatus.TERMINATED, + }, AgentStatus.PAUSED: {AgentStatus.IDLE, AgentStatus.TERMINATED}, AgentStatus.TERMINATED: set(), # Terminal state, no transitions allowed } @@ -870,9 +880,7 @@ async def terminate_agent( agent_name = agent.name # Terminate the agent - terminated_agent = await agent_instance_crud.terminate( - db, instance_id=agent_id - ) + terminated_agent = await agent_instance_crud.terminate(db, instance_id=agent_id) if not terminated_agent: raise NotFoundError( @@ -881,8 +889,7 @@ async def terminate_agent( ) logger.info( - f"User {current_user.email} terminated agent {agent_name} " - f"(id={agent_id})" + f"User {current_user.email} terminated agent {agent_name} (id={agent_id})" ) return MessageResponse( diff --git a/backend/app/api/routes/events.py b/backend/app/api/routes/events.py index 4e4d463..729628d 100644 --- a/backend/app/api/routes/events.py +++ b/backend/app/api/routes/events.py @@ -199,7 +199,9 @@ async def stream_project_events( project_id: UUID, db: "AsyncSession" = Depends(get_db), event_bus: EventBus = Depends(get_event_bus), - token: str | None = Query(None, description="Auth token (for EventSource compatibility)"), + token: str | None = Query( + None, description="Auth token (for EventSource compatibility)" + ), authorization: str | None = Header(None, alias="Authorization"), last_event_id: str | None = Header(None, alias="Last-Event-ID"), ): diff --git a/backend/app/api/routes/issues.py b/backend/app/api/routes/issues.py index 4a94f51..48b550b 100644 --- a/backend/app/api/routes/issues.py +++ b/backend/app/api/routes/issues.py @@ -278,9 +278,7 @@ async def list_issues( assigned_agent_id: UUID | None = Query( None, description="Filter by assigned agent ID" ), - sync_status: SyncStatus | None = Query( - None, description="Filter by sync status" - ), + sync_status: SyncStatus | None = Query(None, description="Filter by sync status"), search: str | None = Query( None, min_length=1, max_length=100, description="Search in title and body" ), @@ -783,9 +781,7 @@ async def assign_issue( updated_issue = await issue_crud.assign_to_agent( db, issue_id=issue_id, agent_id=None ) - logger.info( - f"User {current_user.email} unassigned issue {issue_id}" - ) + logger.info(f"User {current_user.email} unassigned issue {issue_id}") if not updated_issue: raise NotFoundError( diff --git a/backend/app/api/routes/projects.py b/backend/app/api/routes/projects.py index edd051e..0577ba2 100644 --- a/backend/app/api/routes/projects.py +++ b/backend/app/api/routes/projects.py @@ -197,10 +197,10 @@ async def list_projects( status_filter: ProjectStatus | None = Query( None, alias="status", description="Filter by project status" ), - search: str | None = Query(None, description="Search by name, slug, or description"), - all_projects: bool = Query( - False, description="Show all projects (superuser only)" + search: str | None = Query( + None, description="Search by name, slug, or description" ), + all_projects: bool = Query(False, description="Show all projects (superuser only)"), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: @@ -212,7 +212,9 @@ async def list_projects( """ try: # Determine owner filter based on user role and request - owner_id = None if (current_user.is_superuser and all_projects) else current_user.id + owner_id = ( + None if (current_user.is_superuser and all_projects) else current_user.id + ) projects_data, total = await project_crud.get_multi_with_counts( db, @@ -379,13 +381,15 @@ async def update_project( _check_project_ownership(project, current_user) # Update the project - updated_project = await project_crud.update(db, db_obj=project, obj_in=project_in) - logger.info( - f"User {current_user.email} updated project {updated_project.slug}" + updated_project = await project_crud.update( + db, db_obj=project, obj_in=project_in ) + logger.info(f"User {current_user.email} updated project {updated_project.slug}") # Get updated project with counts - project_data = await project_crud.get_with_counts(db, project_id=updated_project.id) + project_data = await project_crud.get_with_counts( + db, project_id=updated_project.id + ) if not project_data: # This shouldn't happen, but handle gracefully @@ -551,7 +555,9 @@ async def pause_project( logger.info(f"User {current_user.email} paused project {project.slug}") # Get project with counts - project_data = await project_crud.get_with_counts(db, project_id=updated_project.id) + project_data = await project_crud.get_with_counts( + db, project_id=updated_project.id + ) if not project_data: raise NotFoundError( @@ -634,7 +640,9 @@ async def resume_project( logger.info(f"User {current_user.email} resumed project {project.slug}") # Get project with counts - project_data = await project_crud.get_with_counts(db, project_id=updated_project.id) + project_data = await project_crud.get_with_counts( + db, project_id=updated_project.id + ) if not project_data: raise NotFoundError( diff --git a/backend/app/api/routes/sprints.py b/backend/app/api/routes/sprints.py index 5ed71b1..b04f3f1 100644 --- a/backend/app/api/routes/sprints.py +++ b/backend/app/api/routes/sprints.py @@ -320,7 +320,9 @@ async def list_sprints( return PaginatedResponse(data=sprint_responses, pagination=pagination_meta) except Exception as e: - logger.error(f"Error listing sprints for project {project_id}: {e!s}", exc_info=True) + logger.error( + f"Error listing sprints for project {project_id}: {e!s}", exc_info=True + ) raise @@ -564,7 +566,9 @@ async def update_sprint( ) # Update the sprint - updated_sprint = await sprint_crud.update(db, db_obj=sprint, obj_in=sprint_update) + updated_sprint = await sprint_crud.update( + db, db_obj=sprint, obj_in=sprint_update + ) logger.info( f"User {current_user.id} updated sprint {sprint_id} in project {project_id}" @@ -1123,7 +1127,9 @@ async def remove_issue_from_sprint( request: Request, project_id: UUID, sprint_id: UUID, - issue_id: UUID = Query(..., description="ID of the issue to remove from the sprint"), + issue_id: UUID = Query( + ..., description="ID of the issue to remove from the sprint" + ), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Any: diff --git a/backend/app/core/redis.py b/backend/app/core/redis.py index 43da57b..c9520f9 100644 --- a/backend/app/core/redis.py +++ b/backend/app/core/redis.py @@ -243,7 +243,9 @@ class RedisClient: try: client = await self._get_client() result = await client.expire(key, ttl) - logger.debug(f"Cache expire for key: {key} (TTL: {ttl}s, success: {result})") + logger.debug( + f"Cache expire for key: {key} (TTL: {ttl}s, success: {result})" + ) return result except (ConnectionError, TimeoutError) as e: logger.error(f"Redis cache_expire failed for key '{key}': {e}") @@ -323,9 +325,7 @@ class RedisClient: return 0 @asynccontextmanager - async def subscribe( - self, *channels: str - ) -> AsyncGenerator[PubSub, None]: + async def subscribe(self, *channels: str) -> AsyncGenerator[PubSub, None]: """ Subscribe to one or more channels. @@ -353,9 +353,7 @@ class RedisClient: logger.debug(f"Unsubscribed from channels: {channels}") @asynccontextmanager - async def psubscribe( - self, *patterns: str - ) -> AsyncGenerator[PubSub, None]: + async def psubscribe(self, *patterns: str) -> AsyncGenerator[PubSub, None]: """ Subscribe to channels matching patterns. diff --git a/backend/app/crud/syndarix/agent_instance.py b/backend/app/crud/syndarix/agent_instance.py index a7c6ce9..2bb6dd1 100644 --- a/backend/app/crud/syndarix/agent_instance.py +++ b/backend/app/crud/syndarix/agent_instance.py @@ -20,7 +20,9 @@ from app.schemas.syndarix import AgentInstanceCreate, AgentInstanceUpdate logger = logging.getLogger(__name__) -class CRUDAgentInstance(CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstanceUpdate]): +class CRUDAgentInstance( + CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstanceUpdate] +): """Async CRUD operations for AgentInstance model.""" async def create( @@ -91,8 +93,12 @@ class CRUDAgentInstance(CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstan return { "instance": instance, - "agent_type_name": instance.agent_type.name if instance.agent_type else None, - "agent_type_slug": instance.agent_type.slug if instance.agent_type else None, + "agent_type_name": instance.agent_type.name + if instance.agent_type + else None, + "agent_type_slug": instance.agent_type.slug + if instance.agent_type + else None, "project_name": instance.project.name if instance.project else None, "project_slug": instance.project.slug if instance.project else None, "assigned_issues_count": assigned_issues_count, @@ -115,9 +121,7 @@ class CRUDAgentInstance(CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstan ) -> tuple[list[AgentInstance], int]: """Get agent instances for a specific project.""" try: - query = select(AgentInstance).where( - AgentInstance.project_id == project_id - ) + query = select(AgentInstance).where(AgentInstance.project_id == project_id) if status is not None: query = query.where(AgentInstance.status == status) diff --git a/backend/app/crud/syndarix/agent_type.py b/backend/app/crud/syndarix/agent_type.py index ddb220f..f01d38d 100644 --- a/backend/app/crud/syndarix/agent_type.py +++ b/backend/app/crud/syndarix/agent_type.py @@ -22,17 +22,13 @@ class CRUDAgentType(CRUDBase[AgentType, AgentTypeCreate, AgentTypeUpdate]): async def get_by_slug(self, db: AsyncSession, *, slug: str) -> AgentType | None: """Get agent type by slug.""" try: - result = await db.execute( - select(AgentType).where(AgentType.slug == slug) - ) + result = await db.execute(select(AgentType).where(AgentType.slug == slug)) return result.scalar_one_or_none() except Exception as e: logger.error(f"Error getting agent type by slug {slug}: {e!s}") raise - async def create( - self, db: AsyncSession, *, obj_in: AgentTypeCreate - ) -> AgentType: + async def create(self, db: AsyncSession, *, obj_in: AgentTypeCreate) -> AgentType: """Create a new agent type with error handling.""" try: db_obj = AgentType( @@ -57,16 +53,12 @@ class CRUDAgentType(CRUDBase[AgentType, AgentTypeCreate, AgentTypeUpdate]): error_msg = str(e.orig) if hasattr(e, "orig") else str(e) if "slug" in error_msg.lower(): logger.warning(f"Duplicate slug attempted: {obj_in.slug}") - raise ValueError( - f"Agent type with slug '{obj_in.slug}' already exists" - ) + raise ValueError(f"Agent type with slug '{obj_in.slug}' already exists") logger.error(f"Integrity error creating agent type: {error_msg}") raise ValueError(f"Database integrity error: {error_msg}") except Exception as e: await db.rollback() - logger.error( - f"Unexpected error creating agent type: {e!s}", exc_info=True - ) + logger.error(f"Unexpected error creating agent type: {e!s}", exc_info=True) raise async def get_multi_with_filters( @@ -215,9 +207,7 @@ class CRUDAgentType(CRUDBase[AgentType, AgentTypeCreate, AgentTypeUpdate]): return results, total except Exception as e: - logger.error( - f"Error getting agent types with counts: {e!s}", exc_info=True - ) + logger.error(f"Error getting agent types with counts: {e!s}", exc_info=True) raise async def get_by_expertise( diff --git a/backend/app/crud/syndarix/issue.py b/backend/app/crud/syndarix/issue.py index d3bbc5e..8510fa0 100644 --- a/backend/app/crud/syndarix/issue.py +++ b/backend/app/crud/syndarix/issue.py @@ -75,7 +75,9 @@ class CRUDIssue(CRUDBase[Issue, IssueCreate, IssueUpdate]): .options( joinedload(Issue.project), joinedload(Issue.sprint), - joinedload(Issue.assigned_agent).joinedload(AgentInstance.agent_type), + joinedload(Issue.assigned_agent).joinedload( + AgentInstance.agent_type + ), ) .where(Issue.id == issue_id) ) @@ -449,9 +451,7 @@ class CRUDIssue(CRUDBase[Issue, IssueCreate, IssueUpdate]): from sqlalchemy import update result = await db.execute( - update(Issue) - .where(Issue.sprint_id == sprint_id) - .values(sprint_id=None) + update(Issue).where(Issue.sprint_id == sprint_id).values(sprint_id=None) ) await db.commit() return result.rowcount diff --git a/backend/app/crud/syndarix/project.py b/backend/app/crud/syndarix/project.py index 60e11c5..f4a091b 100644 --- a/backend/app/crud/syndarix/project.py +++ b/backend/app/crud/syndarix/project.py @@ -2,11 +2,10 @@ """Async CRUD operations for Project model using SQLAlchemy 2.0 patterns.""" import logging +from datetime import UTC, datetime from typing import Any from uuid import UUID -from datetime import UTC, datetime - from sqlalchemy import func, or_, select, update from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession @@ -234,9 +233,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]): Sprint.status == SprintStatus.ACTIVE, ) ) - active_sprints = { - row.project_id: row.name for row in active_sprints_result - } + active_sprints = {row.project_id: row.name for row in active_sprints_result} # Combine results results = [ @@ -251,9 +248,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]): return results, total except Exception as e: - logger.error( - f"Error getting projects with counts: {e!s}", exc_info=True - ) + logger.error(f"Error getting projects with counts: {e!s}", exc_info=True) raise async def get_projects_by_owner( @@ -293,9 +288,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]): - Unassigns issues from terminated agents """ try: - result = await db.execute( - select(Project).where(Project.id == project_id) - ) + result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() if not project: @@ -361,9 +354,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]): return project except Exception as e: await db.rollback() - logger.error( - f"Error archiving project {project_id}: {e!s}", exc_info=True - ) + logger.error(f"Error archiving project {project_id}: {e!s}", exc_info=True) raise diff --git a/backend/app/crud/syndarix/sprint.py b/backend/app/crud/syndarix/sprint.py index 9f459ea..0f0e284 100644 --- a/backend/app/crud/syndarix/sprint.py +++ b/backend/app/crud/syndarix/sprint.py @@ -193,9 +193,7 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]): try: # Lock the sprint row to prevent concurrent modifications result = await db.execute( - select(Sprint) - .where(Sprint.id == sprint_id) - .with_for_update() + select(Sprint).where(Sprint.id == sprint_id).with_for_update() ) sprint = result.scalar_one_or_none() @@ -257,9 +255,7 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]): try: # Lock the sprint row to prevent concurrent modifications result = await db.execute( - select(Sprint) - .where(Sprint.id == sprint_id) - .with_for_update() + select(Sprint).where(Sprint.id == sprint_id).with_for_update() ) sprint = result.scalar_one_or_none() @@ -308,9 +304,7 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]): try: # Lock the sprint row to prevent concurrent modifications result = await db.execute( - select(Sprint) - .where(Sprint.id == sprint_id) - .with_for_update() + select(Sprint).where(Sprint.id == sprint_id).with_for_update() ) sprint = result.scalar_one_or_none() @@ -425,7 +419,8 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]): { "sprint": sprint, **counts_map.get( - sprint.id, {"issue_count": 0, "open_issues": 0, "completed_issues": 0} + sprint.id, + {"issue_count": 0, "open_issues": 0, "completed_issues": 0}, ), } for sprint in sprints diff --git a/backend/app/models/syndarix/issue.py b/backend/app/models/syndarix/issue.py index 738f9f1..c6edcc9 100644 --- a/backend/app/models/syndarix/issue.py +++ b/backend/app/models/syndarix/issue.py @@ -158,7 +158,11 @@ class Issue(Base, UUIDMixin, TimestampMixin): Index("ix_issues_project_status", "project_id", "status"), Index("ix_issues_project_priority", "project_id", "priority"), Index("ix_issues_project_sprint", "project_id", "sprint_id"), - Index("ix_issues_external_tracker_id", "external_tracker_type", "external_issue_id"), + Index( + "ix_issues_external_tracker_id", + "external_tracker_type", + "external_issue_id", + ), Index("ix_issues_sync_status", "sync_status"), Index("ix_issues_project_agent", "project_id", "assigned_agent_id"), Index("ix_issues_project_type", "project_id", "type"), diff --git a/backend/app/models/syndarix/sprint.py b/backend/app/models/syndarix/sprint.py index 4606d80..a2c58a1 100644 --- a/backend/app/models/syndarix/sprint.py +++ b/backend/app/models/syndarix/sprint.py @@ -5,7 +5,17 @@ Sprint model for Syndarix AI consulting platform. A Sprint represents a time-boxed iteration for organizing and delivering work. """ -from sqlalchemy import Column, Date, Enum, ForeignKey, Index, Integer, String, Text, UniqueConstraint +from sqlalchemy import ( + Column, + Date, + Enum, + ForeignKey, + Index, + Integer, + String, + Text, + UniqueConstraint, +) from sqlalchemy.dialects.postgresql import UUID as PGUUID from sqlalchemy.orm import relationship diff --git a/backend/app/schemas/events.py b/backend/app/schemas/events.py index a7a8a65..d241ed0 100644 --- a/backend/app/schemas/events.py +++ b/backend/app/schemas/events.py @@ -205,9 +205,7 @@ class SprintCompletedPayload(BaseModel): sprint_id: UUID = Field(..., description="Sprint ID") sprint_name: str = Field(..., description="Sprint name") completed_issues: int = Field(default=0, description="Number of completed issues") - incomplete_issues: int = Field( - default=0, description="Number of incomplete issues" - ) + incomplete_issues: int = Field(default=0, description="Number of incomplete issues") class ApprovalRequestedPayload(BaseModel): diff --git a/backend/app/schemas/syndarix/issue.py b/backend/app/schemas/syndarix/issue.py index 2e14ea7..3d2bcd3 100644 --- a/backend/app/schemas/syndarix/issue.py +++ b/backend/app/schemas/syndarix/issue.py @@ -99,9 +99,7 @@ class IssueAssign(BaseModel): def validate_assignment(self) -> "IssueAssign": """Ensure only one type of assignee is set.""" if self.assigned_agent_id and self.human_assignee: - raise ValueError( - "Cannot assign to both an agent and a human. Choose one." - ) + raise ValueError("Cannot assign to both an agent and a human. Choose one.") return self diff --git a/backend/app/services/event_bus.py b/backend/app/services/event_bus.py index 27bff16..ebde193 100644 --- a/backend/app/services/event_bus.py +++ b/backend/app/services/event_bus.py @@ -54,22 +54,18 @@ class EventBusError(Exception): """Base exception for EventBus errors.""" - class EventBusConnectionError(EventBusError): """Raised when connection to Redis fails.""" - class EventBusPublishError(EventBusError): """Raised when publishing an event fails.""" - class EventBusSubscriptionError(EventBusError): """Raised when subscribing to channels fails.""" - class EventBus: """ EventBus for Redis Pub/Sub communication. diff --git a/backend/app/services/oauth_service.py b/backend/app/services/oauth_service.py index 561a4bf..d45d539 100644 --- a/backend/app/services/oauth_service.py +++ b/backend/app/services/oauth_service.py @@ -343,7 +343,9 @@ class OAuthService: await oauth_account.update_tokens( db, account=existing_oauth, - access_token_encrypted=token.get("access_token"), refresh_token_encrypted=token.get("refresh_token"), token_expires_at=datetime.now(UTC) + access_token_encrypted=token.get("access_token"), + refresh_token_encrypted=token.get("refresh_token"), + token_expires_at=datetime.now(UTC) + timedelta(seconds=token.get("expires_in", 3600)), ) @@ -375,7 +377,9 @@ class OAuthService: provider=provider, provider_user_id=provider_user_id, provider_email=provider_email, - access_token_encrypted=token.get("access_token"), refresh_token_encrypted=token.get("refresh_token"), token_expires_at=datetime.now(UTC) + access_token_encrypted=token.get("access_token"), + refresh_token_encrypted=token.get("refresh_token"), + token_expires_at=datetime.now(UTC) + timedelta(seconds=token.get("expires_in", 3600)) if token.get("expires_in") else None, @@ -644,7 +648,9 @@ class OAuthService: provider=provider, provider_user_id=provider_user_id, provider_email=email, - access_token_encrypted=token.get("access_token"), refresh_token_encrypted=token.get("refresh_token"), token_expires_at=datetime.now(UTC) + access_token_encrypted=token.get("access_token"), + refresh_token_encrypted=token.get("refresh_token"), + token_expires_at=datetime.now(UTC) + timedelta(seconds=token.get("expires_in", 3600)) if token.get("expires_in") else None, diff --git a/backend/app/tasks/agent.py b/backend/app/tasks/agent.py index c072b33..aaeb90b 100644 --- a/backend/app/tasks/agent.py +++ b/backend/app/tasks/agent.py @@ -91,9 +91,7 @@ def spawn_agent( Returns: dict with status, agent_type_id, and project_id """ - logger.info( - f"Spawning agent of type {agent_type_id} for project {project_id}" - ) + logger.info(f"Spawning agent of type {agent_type_id} for project {project_id}") # TODO: Implement agent spawning # This will involve: @@ -132,9 +130,7 @@ def terminate_agent( Returns: dict with status and agent_instance_id """ - logger.info( - f"Terminating agent instance {agent_instance_id} with reason: {reason}" - ) + logger.info(f"Terminating agent instance {agent_instance_id} with reason: {reason}") # TODO: Implement agent termination # This will involve: diff --git a/backend/app/tasks/git.py b/backend/app/tasks/git.py index fbabf99..febede5 100644 --- a/backend/app/tasks/git.py +++ b/backend/app/tasks/git.py @@ -86,9 +86,7 @@ def commit_changes( Returns: dict with status and project_id """ - logger.info( - f"Committing changes for project {project_id}: {message}" - ) + logger.info(f"Committing changes for project {project_id}: {message}") # TODO: Implement commit operation # This will involve: @@ -209,9 +207,7 @@ def push_changes( Returns: dict with status and project_id """ - logger.info( - f"Pushing branch {branch} for project {project_id} (force={force})" - ) + logger.info(f"Pushing branch {branch} for project {project_id} (force={force})") # TODO: Implement push operation # This will involve: diff --git a/backend/app/tasks/sync.py b/backend/app/tasks/sync.py index 43a0aab..c5821bd 100644 --- a/backend/app/tasks/sync.py +++ b/backend/app/tasks/sync.py @@ -140,9 +140,7 @@ def sync_project_issues( Returns: dict with status and project_id """ - logger.info( - f"Syncing issues for project {project_id} (full={full})" - ) + logger.info(f"Syncing issues for project {project_id} (full={full})") # TODO: Implement project-specific sync # This will involve: @@ -180,9 +178,7 @@ def push_issue_to_external( Returns: dict with status, issue_id, and operation """ - logger.info( - f"Pushing {operation} for issue {issue_id} in project {project_id}" - ) + logger.info(f"Pushing {operation} for issue {issue_id} in project {project_id}") # TODO: Implement outbound sync # This will involve: diff --git a/backend/app/tasks/workflow.py b/backend/app/tasks/workflow.py index 4cf3b58..eaef9dc 100644 --- a/backend/app/tasks/workflow.py +++ b/backend/app/tasks/workflow.py @@ -72,9 +72,7 @@ def execute_workflow_step( Returns: dict with status, workflow_id, and transition """ - logger.info( - f"Executing transition '{transition}' for workflow {workflow_id}" - ) + logger.info(f"Executing transition '{transition}' for workflow {workflow_id}") # TODO: Implement workflow transition # This will involve: @@ -196,9 +194,7 @@ def start_story_workflow( Returns: dict with status and story_id """ - logger.info( - f"Starting story workflow for story {story_id} in project {project_id}" - ) + logger.info(f"Starting story workflow for story {story_id} in project {project_id}") # TODO: Implement story workflow initialization # This will involve: