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.
This commit is contained in:
2026-01-03 01:35:18 +01:00
parent da5affd613
commit acd18ff694
26 changed files with 540 additions and 357 deletions

View File

@@ -40,6 +40,7 @@ def include_object(object, name, type_, reflected, compare_to):
return False return False
return True return True
# Interpret the config file for Python logging. # Interpret the config file for Python logging.
# This line sets up loggers basically. # This line sets up loggers basically.
if config.config_file_name is not None: if config.config_file_name is not None:

View File

@@ -5,6 +5,7 @@ Revises:
Create Date: 2025-11-27 09:08:09.464506 Create Date: 2025-11-27 09:08:09.464506
""" """
from collections.abc import Sequence from collections.abc import Sequence
import sqlalchemy as sa import sqlalchemy as sa
@@ -12,7 +13,7 @@ from alembic import op
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '0001' revision: str = "0001"
down_revision: str | None = None down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None branch_labels: str | Sequence[str] | None = None
depends_on: 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: def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('oauth_states', op.create_table(
sa.Column('state', sa.String(length=255), nullable=False), "oauth_states",
sa.Column('code_verifier', sa.String(length=128), nullable=True), sa.Column("state", sa.String(length=255), nullable=False),
sa.Column('nonce', sa.String(length=255), nullable=True), sa.Column("code_verifier", sa.String(length=128), nullable=True),
sa.Column('provider', sa.String(length=50), nullable=False), sa.Column("nonce", sa.String(length=255), nullable=True),
sa.Column('redirect_uri', sa.String(length=500), nullable=True), sa.Column("provider", sa.String(length=50), nullable=False),
sa.Column('user_id', sa.UUID(), nullable=True), sa.Column("redirect_uri", sa.String(length=500), nullable=True),
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False), sa.Column("user_id", sa.UUID(), nullable=True),
sa.Column('id', sa.UUID(), nullable=False), sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), sa.Column("id", sa.UUID(), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint('id') 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.create_table('organizations', op.f("ix_oauth_states_state"), "oauth_states", ["state"], unique=True
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_organizations_is_active'), 'organizations', ['is_active'], unique=False) op.create_table(
op.create_index(op.f('ix_organizations_name'), 'organizations', ['name'], unique=False) "organizations",
op.create_index('ix_organizations_name_active', 'organizations', ['name', 'is_active'], unique=False) sa.Column("name", sa.String(length=255), nullable=False),
op.create_index(op.f('ix_organizations_slug'), 'organizations', ['slug'], unique=True) sa.Column("slug", sa.String(length=255), nullable=False),
op.create_index('ix_organizations_slug_active', 'organizations', ['slug', 'is_active'], unique=False) sa.Column("description", sa.Text(), nullable=True),
op.create_table('users', sa.Column("is_active", sa.Boolean(), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False), sa.Column("settings", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('password_hash', sa.String(length=255), nullable=True), sa.Column("id", sa.UUID(), nullable=False),
sa.Column('first_name', sa.String(length=100), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column('last_name', sa.String(length=100), nullable=True), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.Column('phone_number', sa.String(length=20), nullable=True), sa.PrimaryKeyConstraint("id"),
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(op.f('ix_users_deleted_at'), 'users', ['deleted_at'], unique=False) op.create_index(
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) op.f("ix_organizations_is_active"), "organizations", ["is_active"], unique=False
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.create_index(op.f('ix_oauth_accounts_provider_email'), 'oauth_accounts', ['provider_email'], unique=False) op.f("ix_organizations_name"), "organizations", ["name"], 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.create_index(op.f('ix_oauth_clients_is_active'), 'oauth_clients', ['is_active'], unique=False) "ix_organizations_name_active",
op.create_table('user_organizations', "organizations",
sa.Column('user_id', sa.UUID(), nullable=False), ["name", "is_active"],
sa.Column('organization_id', sa.UUID(), nullable=False), unique=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(
op.create_index('ix_user_org_role', 'user_organizations', ['role'], unique=False) op.f("ix_organizations_slug"), "organizations", ["slug"], unique=True
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(
op.create_index('ix_user_sessions_jti_active', 'user_sessions', ['refresh_token_jti', 'is_active'], unique=False) "ix_organizations_slug_active",
op.create_index(op.f('ix_user_sessions_refresh_token_jti'), 'user_sessions', ['refresh_token_jti'], unique=True) "organizations",
op.create_index('ix_user_sessions_user_active', 'user_sessions', ['user_id', 'is_active'], unique=False) ["slug", "is_active"],
op.create_index(op.f('ix_user_sessions_user_id'), 'user_sessions', ['user_id'], unique=False) 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_table(
op.create_index(op.f('ix_oauth_authorization_codes_code'), 'oauth_authorization_codes', ['code'], unique=True) "users",
op.create_index('ix_oauth_authorization_codes_expires_at', 'oauth_authorization_codes', ['expires_at'], unique=False) sa.Column("email", sa.String(length=255), nullable=False),
op.create_table('oauth_consents', sa.Column("password_hash", sa.String(length=255), nullable=True),
sa.Column('user_id', sa.UUID(), nullable=False), sa.Column("first_name", sa.String(length=100), nullable=False),
sa.Column('client_id', sa.String(length=64), nullable=False), sa.Column("last_name", sa.String(length=100), nullable=True),
sa.Column('granted_scopes', sa.String(length=1000), nullable=False), sa.Column("phone_number", sa.String(length=20), nullable=True),
sa.Column('id', sa.UUID(), nullable=False), sa.Column("is_active", sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), sa.Column("is_superuser", sa.Boolean(), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), sa.Column(
sa.ForeignKeyConstraint(['client_id'], ['oauth_clients.client_id'], ondelete='CASCADE'), "preferences", postgresql.JSONB(astext_type=sa.Text()), nullable=True
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), ),
sa.PrimaryKeyConstraint('id') 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_index(op.f("ix_users_deleted_at"), "users", ["deleted_at"], unique=False)
op.create_table('oauth_provider_refresh_tokens', op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
sa.Column('token_hash', sa.String(length=64), nullable=False), op.create_index(op.f("ix_users_is_active"), "users", ["is_active"], unique=False)
sa.Column('jti', sa.String(length=64), nullable=False), op.create_index(
sa.Column('client_id', sa.String(length=64), nullable=False), op.f("ix_users_is_superuser"), "users", ["is_superuser"], unique=False
sa.Column('user_id', sa.UUID(), nullable=False), )
sa.Column('scope', sa.String(length=1000), nullable=False), op.create_index(op.f("ix_users_locale"), "users", ["locale"], unique=False)
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False), op.create_table(
sa.Column('revoked', sa.Boolean(), nullable=False), "oauth_accounts",
sa.Column('last_used_at', sa.DateTime(timezone=True), nullable=True), sa.Column("user_id", sa.UUID(), nullable=False),
sa.Column('device_info', sa.String(length=500), nullable=True), sa.Column("provider", sa.String(length=50), nullable=False),
sa.Column('ip_address', sa.String(length=45), nullable=True), sa.Column("provider_user_id", sa.String(length=255), nullable=False),
sa.Column('id', sa.UUID(), nullable=False), sa.Column("provider_email", sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), sa.Column("access_token_encrypted", sa.String(length=2048), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), sa.Column("refresh_token_encrypted", sa.String(length=2048), nullable=True),
sa.ForeignKeyConstraint(['client_id'], ['oauth_clients.client_id'], ondelete='CASCADE'), sa.Column("token_expires_at", sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), sa.Column("id", sa.UUID(), nullable=False),
sa.PrimaryKeyConstraint('id') 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 ### # ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ### # ### 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.drop_index(op.f('ix_oauth_provider_refresh_tokens_token_hash'), table_name='oauth_provider_refresh_tokens') "ix_oauth_provider_refresh_tokens_user_revoked",
op.drop_index(op.f('ix_oauth_provider_refresh_tokens_revoked'), table_name='oauth_provider_refresh_tokens') 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(
op.drop_index('ix_oauth_provider_refresh_tokens_client_user', table_name='oauth_provider_refresh_tokens') op.f("ix_oauth_provider_refresh_tokens_token_hash"),
op.drop_table('oauth_provider_refresh_tokens') table_name="oauth_provider_refresh_tokens",
op.drop_index('ix_oauth_consents_user_client', table_name='oauth_consents') )
op.drop_table('oauth_consents') op.drop_index(
op.drop_index('ix_oauth_authorization_codes_expires_at', table_name='oauth_authorization_codes') op.f("ix_oauth_provider_refresh_tokens_revoked"),
op.drop_index(op.f('ix_oauth_authorization_codes_code'), table_name='oauth_authorization_codes') table_name="oauth_provider_refresh_tokens",
op.drop_index('ix_oauth_authorization_codes_client_user', table_name='oauth_authorization_codes') )
op.drop_table('oauth_authorization_codes') op.drop_index(
op.drop_index(op.f('ix_user_sessions_user_id'), table_name='user_sessions') op.f("ix_oauth_provider_refresh_tokens_jti"),
op.drop_index('ix_user_sessions_user_active', table_name='user_sessions') table_name="oauth_provider_refresh_tokens",
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.drop_index(op.f('ix_user_sessions_is_active'), table_name='user_sessions') "ix_oauth_provider_refresh_tokens_expires_at",
op.drop_table('user_sessions') table_name="oauth_provider_refresh_tokens",
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(
op.drop_index('ix_user_org_role', table_name='user_organizations') "ix_oauth_provider_refresh_tokens_client_user",
op.drop_index('ix_user_org_org_active', table_name='user_organizations') table_name="oauth_provider_refresh_tokens",
op.drop_table('user_organizations') )
op.drop_index(op.f('ix_oauth_clients_is_active'), table_name='oauth_clients') op.drop_table("oauth_provider_refresh_tokens")
op.drop_index(op.f('ix_oauth_clients_client_id'), table_name='oauth_clients') op.drop_index("ix_oauth_consents_user_client", table_name="oauth_consents")
op.drop_table('oauth_clients') op.drop_table("oauth_consents")
op.drop_index('ix_oauth_accounts_user_provider', table_name='oauth_accounts') op.drop_index(
op.drop_index(op.f('ix_oauth_accounts_user_id'), table_name='oauth_accounts') "ix_oauth_authorization_codes_expires_at",
op.drop_index(op.f('ix_oauth_accounts_provider_email'), table_name='oauth_accounts') table_name="oauth_authorization_codes",
op.drop_index(op.f('ix_oauth_accounts_provider'), table_name='oauth_accounts') )
op.drop_table('oauth_accounts') op.drop_index(
op.drop_index(op.f('ix_users_locale'), table_name='users') op.f("ix_oauth_authorization_codes_code"),
op.drop_index(op.f('ix_users_is_superuser'), table_name='users') table_name="oauth_authorization_codes",
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.drop_index(op.f('ix_users_deleted_at'), table_name='users') "ix_oauth_authorization_codes_client_user",
op.drop_table('users') table_name="oauth_authorization_codes",
op.drop_index('ix_organizations_slug_active', table_name='organizations') )
op.drop_index(op.f('ix_organizations_slug'), table_name='organizations') op.drop_table("oauth_authorization_codes")
op.drop_index('ix_organizations_name_active', table_name='organizations') op.drop_index(op.f("ix_user_sessions_user_id"), table_name="user_sessions")
op.drop_index(op.f('ix_organizations_name'), table_name='organizations') op.drop_index("ix_user_sessions_user_active", table_name="user_sessions")
op.drop_index(op.f('ix_organizations_is_active'), table_name='organizations') op.drop_index(
op.drop_table('organizations') op.f("ix_user_sessions_refresh_token_jti"), table_name="user_sessions"
op.drop_index(op.f('ix_oauth_states_state'), table_name='oauth_states') )
op.drop_table('oauth_states') 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 ### # ### end Alembic commands ###

View File

@@ -114,8 +114,13 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
# Drop indexes in reverse order # Drop indexes in reverse order
op.drop_index("ix_perf_oauth_auth_codes_expires", table_name="oauth_authorization_codes") op.drop_index(
op.drop_index("ix_perf_oauth_refresh_tokens_expires", table_name="oauth_provider_refresh_tokens") "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_user_sessions_expires", table_name="user_sessions")
op.drop_index("ix_perf_organizations_slug_lower", table_name="organizations") op.drop_index("ix_perf_organizations_slug_lower", table_name="organizations")
op.drop_index("ix_perf_users_active", table_name="users") op.drop_index("ix_perf_users_active", table_name="users")

View File

@@ -443,9 +443,7 @@ def upgrade() -> None:
), ),
sa.PrimaryKeyConstraint("id"), sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"), sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint( sa.ForeignKeyConstraint(["parent_id"], ["issues.id"], ondelete="CASCADE"),
["parent_id"], ["issues.id"], ondelete="CASCADE"
),
sa.ForeignKeyConstraint(["sprint_id"], ["sprints.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["sprint_id"], ["sprints.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint( sa.ForeignKeyConstraint(
["assigned_agent_id"], ["agent_instances.id"], ondelete="SET NULL" ["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_human_assignee", "issues", ["human_assignee"])
op.create_index("ix_issues_sprint_id", "issues", ["sprint_id"]) 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_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_sync_status", "issues", ["sync_status"])
op.create_index("ix_issues_closed_at", "issues", ["closed_at"]) op.create_index("ix_issues_closed_at", "issues", ["closed_at"])
# Composite indexes # 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_priority", "issues", ["project_id", "priority"])
op.create_index("ix_issues_project_sprint", "issues", ["project_id", "sprint_id"]) 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_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( op.create_index(
"ix_issues_project_status_priority", "ix_issues_project_status_priority",
"issues", "issues",

View File

@@ -32,9 +32,7 @@ api_router.include_router(
api_router.include_router(events.router, tags=["Events"]) api_router.include_router(events.router, tags=["Events"])
# Syndarix domain routers # Syndarix domain routers
api_router.include_router( api_router.include_router(projects.router, prefix="/projects", tags=["Projects"])
projects.router, prefix="/projects", tags=["Projects"]
)
api_router.include_router( api_router.include_router(
agent_types.router, prefix="/agent-types", tags=["Agent Types"] agent_types.router, prefix="/agent-types", tags=["Agent Types"]
) )

View File

@@ -57,8 +57,18 @@ RATE_MULTIPLIER = 100 if IS_TEST else 1
# Valid status transitions for agent lifecycle management # Valid status transitions for agent lifecycle management
VALID_STATUS_TRANSITIONS: dict[AgentStatus, set[AgentStatus]] = { VALID_STATUS_TRANSITIONS: dict[AgentStatus, set[AgentStatus]] = {
AgentStatus.IDLE: {AgentStatus.WORKING, AgentStatus.PAUSED, AgentStatus.TERMINATED}, AgentStatus.IDLE: {AgentStatus.WORKING, AgentStatus.PAUSED, AgentStatus.TERMINATED},
AgentStatus.WORKING: {AgentStatus.IDLE, AgentStatus.WAITING, AgentStatus.PAUSED, AgentStatus.TERMINATED}, AgentStatus.WORKING: {
AgentStatus.WAITING: {AgentStatus.IDLE, AgentStatus.WORKING, AgentStatus.PAUSED, AgentStatus.TERMINATED}, 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.PAUSED: {AgentStatus.IDLE, AgentStatus.TERMINATED},
AgentStatus.TERMINATED: set(), # Terminal state, no transitions allowed AgentStatus.TERMINATED: set(), # Terminal state, no transitions allowed
} }
@@ -870,9 +880,7 @@ async def terminate_agent(
agent_name = agent.name agent_name = agent.name
# Terminate the agent # Terminate the agent
terminated_agent = await agent_instance_crud.terminate( terminated_agent = await agent_instance_crud.terminate(db, instance_id=agent_id)
db, instance_id=agent_id
)
if not terminated_agent: if not terminated_agent:
raise NotFoundError( raise NotFoundError(
@@ -881,8 +889,7 @@ async def terminate_agent(
) )
logger.info( logger.info(
f"User {current_user.email} terminated agent {agent_name} " f"User {current_user.email} terminated agent {agent_name} (id={agent_id})"
f"(id={agent_id})"
) )
return MessageResponse( return MessageResponse(

View File

@@ -199,7 +199,9 @@ async def stream_project_events(
project_id: UUID, project_id: UUID,
db: "AsyncSession" = Depends(get_db), db: "AsyncSession" = Depends(get_db),
event_bus: EventBus = Depends(get_event_bus), 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"), authorization: str | None = Header(None, alias="Authorization"),
last_event_id: str | None = Header(None, alias="Last-Event-ID"), last_event_id: str | None = Header(None, alias="Last-Event-ID"),
): ):

View File

@@ -278,9 +278,7 @@ async def list_issues(
assigned_agent_id: UUID | None = Query( assigned_agent_id: UUID | None = Query(
None, description="Filter by assigned agent ID" None, description="Filter by assigned agent ID"
), ),
sync_status: SyncStatus | None = Query( sync_status: SyncStatus | None = Query(None, description="Filter by sync status"),
None, description="Filter by sync status"
),
search: str | None = Query( search: str | None = Query(
None, min_length=1, max_length=100, description="Search in title and body" 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( updated_issue = await issue_crud.assign_to_agent(
db, issue_id=issue_id, agent_id=None db, issue_id=issue_id, agent_id=None
) )
logger.info( logger.info(f"User {current_user.email} unassigned issue {issue_id}")
f"User {current_user.email} unassigned issue {issue_id}"
)
if not updated_issue: if not updated_issue:
raise NotFoundError( raise NotFoundError(

View File

@@ -197,10 +197,10 @@ async def list_projects(
status_filter: ProjectStatus | None = Query( status_filter: ProjectStatus | None = Query(
None, alias="status", description="Filter by project status" None, alias="status", description="Filter by project status"
), ),
search: str | None = Query(None, description="Search by name, slug, or description"), search: str | None = Query(
all_projects: bool = Query( None, description="Search by name, slug, or description"
False, description="Show all projects (superuser only)"
), ),
all_projects: bool = Query(False, description="Show all projects (superuser only)"),
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> Any: ) -> Any:
@@ -212,7 +212,9 @@ async def list_projects(
""" """
try: try:
# Determine owner filter based on user role and request # 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( projects_data, total = await project_crud.get_multi_with_counts(
db, db,
@@ -379,13 +381,15 @@ async def update_project(
_check_project_ownership(project, current_user) _check_project_ownership(project, current_user)
# Update the project # Update the project
updated_project = await project_crud.update(db, db_obj=project, obj_in=project_in) updated_project = await project_crud.update(
logger.info( db, db_obj=project, obj_in=project_in
f"User {current_user.email} updated project {updated_project.slug}"
) )
logger.info(f"User {current_user.email} updated project {updated_project.slug}")
# Get updated project with counts # 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: if not project_data:
# This shouldn't happen, but handle gracefully # 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}") logger.info(f"User {current_user.email} paused project {project.slug}")
# Get project with counts # 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: if not project_data:
raise NotFoundError( raise NotFoundError(
@@ -634,7 +640,9 @@ async def resume_project(
logger.info(f"User {current_user.email} resumed project {project.slug}") logger.info(f"User {current_user.email} resumed project {project.slug}")
# Get project with counts # 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: if not project_data:
raise NotFoundError( raise NotFoundError(

View File

@@ -320,7 +320,9 @@ async def list_sprints(
return PaginatedResponse(data=sprint_responses, pagination=pagination_meta) return PaginatedResponse(data=sprint_responses, pagination=pagination_meta)
except Exception as e: 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 raise
@@ -564,7 +566,9 @@ async def update_sprint(
) )
# Update the 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( logger.info(
f"User {current_user.id} updated sprint {sprint_id} in project {project_id}" 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, request: Request,
project_id: UUID, project_id: UUID,
sprint_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), current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> Any: ) -> Any:

View File

@@ -243,7 +243,9 @@ class RedisClient:
try: try:
client = await self._get_client() client = await self._get_client()
result = await client.expire(key, ttl) 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 return result
except (ConnectionError, TimeoutError) as e: except (ConnectionError, TimeoutError) as e:
logger.error(f"Redis cache_expire failed for key '{key}': {e}") logger.error(f"Redis cache_expire failed for key '{key}': {e}")
@@ -323,9 +325,7 @@ class RedisClient:
return 0 return 0
@asynccontextmanager @asynccontextmanager
async def subscribe( async def subscribe(self, *channels: str) -> AsyncGenerator[PubSub, None]:
self, *channels: str
) -> AsyncGenerator[PubSub, None]:
""" """
Subscribe to one or more channels. Subscribe to one or more channels.
@@ -353,9 +353,7 @@ class RedisClient:
logger.debug(f"Unsubscribed from channels: {channels}") logger.debug(f"Unsubscribed from channels: {channels}")
@asynccontextmanager @asynccontextmanager
async def psubscribe( async def psubscribe(self, *patterns: str) -> AsyncGenerator[PubSub, None]:
self, *patterns: str
) -> AsyncGenerator[PubSub, None]:
""" """
Subscribe to channels matching patterns. Subscribe to channels matching patterns.

View File

@@ -20,7 +20,9 @@ from app.schemas.syndarix import AgentInstanceCreate, AgentInstanceUpdate
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CRUDAgentInstance(CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstanceUpdate]): class CRUDAgentInstance(
CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstanceUpdate]
):
"""Async CRUD operations for AgentInstance model.""" """Async CRUD operations for AgentInstance model."""
async def create( async def create(
@@ -91,8 +93,12 @@ class CRUDAgentInstance(CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstan
return { return {
"instance": instance, "instance": instance,
"agent_type_name": instance.agent_type.name if instance.agent_type else None, "agent_type_name": instance.agent_type.name
"agent_type_slug": instance.agent_type.slug if instance.agent_type else None, 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_name": instance.project.name if instance.project else None,
"project_slug": instance.project.slug if instance.project else None, "project_slug": instance.project.slug if instance.project else None,
"assigned_issues_count": assigned_issues_count, "assigned_issues_count": assigned_issues_count,
@@ -115,9 +121,7 @@ class CRUDAgentInstance(CRUDBase[AgentInstance, AgentInstanceCreate, AgentInstan
) -> tuple[list[AgentInstance], int]: ) -> tuple[list[AgentInstance], int]:
"""Get agent instances for a specific project.""" """Get agent instances for a specific project."""
try: try:
query = select(AgentInstance).where( query = select(AgentInstance).where(AgentInstance.project_id == project_id)
AgentInstance.project_id == project_id
)
if status is not None: if status is not None:
query = query.where(AgentInstance.status == status) query = query.where(AgentInstance.status == status)

View File

@@ -22,17 +22,13 @@ class CRUDAgentType(CRUDBase[AgentType, AgentTypeCreate, AgentTypeUpdate]):
async def get_by_slug(self, db: AsyncSession, *, slug: str) -> AgentType | None: async def get_by_slug(self, db: AsyncSession, *, slug: str) -> AgentType | None:
"""Get agent type by slug.""" """Get agent type by slug."""
try: try:
result = await db.execute( result = await db.execute(select(AgentType).where(AgentType.slug == slug))
select(AgentType).where(AgentType.slug == slug)
)
return result.scalar_one_or_none() return result.scalar_one_or_none()
except Exception as e: except Exception as e:
logger.error(f"Error getting agent type by slug {slug}: {e!s}") logger.error(f"Error getting agent type by slug {slug}: {e!s}")
raise raise
async def create( async def create(self, db: AsyncSession, *, obj_in: AgentTypeCreate) -> AgentType:
self, db: AsyncSession, *, obj_in: AgentTypeCreate
) -> AgentType:
"""Create a new agent type with error handling.""" """Create a new agent type with error handling."""
try: try:
db_obj = AgentType( 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) error_msg = str(e.orig) if hasattr(e, "orig") else str(e)
if "slug" in error_msg.lower(): if "slug" in error_msg.lower():
logger.warning(f"Duplicate slug attempted: {obj_in.slug}") logger.warning(f"Duplicate slug attempted: {obj_in.slug}")
raise ValueError( raise ValueError(f"Agent type with slug '{obj_in.slug}' already exists")
f"Agent type with slug '{obj_in.slug}' already exists"
)
logger.error(f"Integrity error creating agent type: {error_msg}") logger.error(f"Integrity error creating agent type: {error_msg}")
raise ValueError(f"Database integrity error: {error_msg}") raise ValueError(f"Database integrity error: {error_msg}")
except Exception as e: except Exception as e:
await db.rollback() await db.rollback()
logger.error( logger.error(f"Unexpected error creating agent type: {e!s}", exc_info=True)
f"Unexpected error creating agent type: {e!s}", exc_info=True
)
raise raise
async def get_multi_with_filters( async def get_multi_with_filters(
@@ -215,9 +207,7 @@ class CRUDAgentType(CRUDBase[AgentType, AgentTypeCreate, AgentTypeUpdate]):
return results, total return results, total
except Exception as e: except Exception as e:
logger.error( logger.error(f"Error getting agent types with counts: {e!s}", exc_info=True)
f"Error getting agent types with counts: {e!s}", exc_info=True
)
raise raise
async def get_by_expertise( async def get_by_expertise(

View File

@@ -75,7 +75,9 @@ class CRUDIssue(CRUDBase[Issue, IssueCreate, IssueUpdate]):
.options( .options(
joinedload(Issue.project), joinedload(Issue.project),
joinedload(Issue.sprint), joinedload(Issue.sprint),
joinedload(Issue.assigned_agent).joinedload(AgentInstance.agent_type), joinedload(Issue.assigned_agent).joinedload(
AgentInstance.agent_type
),
) )
.where(Issue.id == issue_id) .where(Issue.id == issue_id)
) )
@@ -449,9 +451,7 @@ class CRUDIssue(CRUDBase[Issue, IssueCreate, IssueUpdate]):
from sqlalchemy import update from sqlalchemy import update
result = await db.execute( result = await db.execute(
update(Issue) update(Issue).where(Issue.sprint_id == sprint_id).values(sprint_id=None)
.where(Issue.sprint_id == sprint_id)
.values(sprint_id=None)
) )
await db.commit() await db.commit()
return result.rowcount return result.rowcount

View File

@@ -2,11 +2,10 @@
"""Async CRUD operations for Project model using SQLAlchemy 2.0 patterns.""" """Async CRUD operations for Project model using SQLAlchemy 2.0 patterns."""
import logging import logging
from datetime import UTC, datetime
from typing import Any from typing import Any
from uuid import UUID from uuid import UUID
from datetime import UTC, datetime
from sqlalchemy import func, or_, select, update from sqlalchemy import func, or_, select, update
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -234,9 +233,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]):
Sprint.status == SprintStatus.ACTIVE, Sprint.status == SprintStatus.ACTIVE,
) )
) )
active_sprints = { active_sprints = {row.project_id: row.name for row in active_sprints_result}
row.project_id: row.name for row in active_sprints_result
}
# Combine results # Combine results
results = [ results = [
@@ -251,9 +248,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]):
return results, total return results, total
except Exception as e: except Exception as e:
logger.error( logger.error(f"Error getting projects with counts: {e!s}", exc_info=True)
f"Error getting projects with counts: {e!s}", exc_info=True
)
raise raise
async def get_projects_by_owner( async def get_projects_by_owner(
@@ -293,9 +288,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]):
- Unassigns issues from terminated agents - Unassigns issues from terminated agents
""" """
try: try:
result = await db.execute( result = await db.execute(select(Project).where(Project.id == project_id))
select(Project).where(Project.id == project_id)
)
project = result.scalar_one_or_none() project = result.scalar_one_or_none()
if not project: if not project:
@@ -361,9 +354,7 @@ class CRUDProject(CRUDBase[Project, ProjectCreate, ProjectUpdate]):
return project return project
except Exception as e: except Exception as e:
await db.rollback() await db.rollback()
logger.error( logger.error(f"Error archiving project {project_id}: {e!s}", exc_info=True)
f"Error archiving project {project_id}: {e!s}", exc_info=True
)
raise raise

View File

@@ -193,9 +193,7 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]):
try: try:
# Lock the sprint row to prevent concurrent modifications # Lock the sprint row to prevent concurrent modifications
result = await db.execute( result = await db.execute(
select(Sprint) select(Sprint).where(Sprint.id == sprint_id).with_for_update()
.where(Sprint.id == sprint_id)
.with_for_update()
) )
sprint = result.scalar_one_or_none() sprint = result.scalar_one_or_none()
@@ -257,9 +255,7 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]):
try: try:
# Lock the sprint row to prevent concurrent modifications # Lock the sprint row to prevent concurrent modifications
result = await db.execute( result = await db.execute(
select(Sprint) select(Sprint).where(Sprint.id == sprint_id).with_for_update()
.where(Sprint.id == sprint_id)
.with_for_update()
) )
sprint = result.scalar_one_or_none() sprint = result.scalar_one_or_none()
@@ -308,9 +304,7 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]):
try: try:
# Lock the sprint row to prevent concurrent modifications # Lock the sprint row to prevent concurrent modifications
result = await db.execute( result = await db.execute(
select(Sprint) select(Sprint).where(Sprint.id == sprint_id).with_for_update()
.where(Sprint.id == sprint_id)
.with_for_update()
) )
sprint = result.scalar_one_or_none() sprint = result.scalar_one_or_none()
@@ -425,7 +419,8 @@ class CRUDSprint(CRUDBase[Sprint, SprintCreate, SprintUpdate]):
{ {
"sprint": sprint, "sprint": sprint,
**counts_map.get( **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 for sprint in sprints

View File

@@ -158,7 +158,11 @@ class Issue(Base, UUIDMixin, TimestampMixin):
Index("ix_issues_project_status", "project_id", "status"), Index("ix_issues_project_status", "project_id", "status"),
Index("ix_issues_project_priority", "project_id", "priority"), Index("ix_issues_project_priority", "project_id", "priority"),
Index("ix_issues_project_sprint", "project_id", "sprint_id"), 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_sync_status", "sync_status"),
Index("ix_issues_project_agent", "project_id", "assigned_agent_id"), Index("ix_issues_project_agent", "project_id", "assigned_agent_id"),
Index("ix_issues_project_type", "project_id", "type"), Index("ix_issues_project_type", "project_id", "type"),

View File

@@ -5,7 +5,17 @@ Sprint model for Syndarix AI consulting platform.
A Sprint represents a time-boxed iteration for organizing and delivering work. 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.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship

View File

@@ -205,9 +205,7 @@ class SprintCompletedPayload(BaseModel):
sprint_id: UUID = Field(..., description="Sprint ID") sprint_id: UUID = Field(..., description="Sprint ID")
sprint_name: str = Field(..., description="Sprint name") sprint_name: str = Field(..., description="Sprint name")
completed_issues: int = Field(default=0, description="Number of completed issues") completed_issues: int = Field(default=0, description="Number of completed issues")
incomplete_issues: int = Field( incomplete_issues: int = Field(default=0, description="Number of incomplete issues")
default=0, description="Number of incomplete issues"
)
class ApprovalRequestedPayload(BaseModel): class ApprovalRequestedPayload(BaseModel):

View File

@@ -99,9 +99,7 @@ class IssueAssign(BaseModel):
def validate_assignment(self) -> "IssueAssign": def validate_assignment(self) -> "IssueAssign":
"""Ensure only one type of assignee is set.""" """Ensure only one type of assignee is set."""
if self.assigned_agent_id and self.human_assignee: if self.assigned_agent_id and self.human_assignee:
raise ValueError( raise ValueError("Cannot assign to both an agent and a human. Choose one.")
"Cannot assign to both an agent and a human. Choose one."
)
return self return self

View File

@@ -54,22 +54,18 @@ class EventBusError(Exception):
"""Base exception for EventBus errors.""" """Base exception for EventBus errors."""
class EventBusConnectionError(EventBusError): class EventBusConnectionError(EventBusError):
"""Raised when connection to Redis fails.""" """Raised when connection to Redis fails."""
class EventBusPublishError(EventBusError): class EventBusPublishError(EventBusError):
"""Raised when publishing an event fails.""" """Raised when publishing an event fails."""
class EventBusSubscriptionError(EventBusError): class EventBusSubscriptionError(EventBusError):
"""Raised when subscribing to channels fails.""" """Raised when subscribing to channels fails."""
class EventBus: class EventBus:
""" """
EventBus for Redis Pub/Sub communication. EventBus for Redis Pub/Sub communication.

View File

@@ -343,7 +343,9 @@ class OAuthService:
await oauth_account.update_tokens( await oauth_account.update_tokens(
db, db,
account=existing_oauth, 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)), + timedelta(seconds=token.get("expires_in", 3600)),
) )
@@ -375,7 +377,9 @@ class OAuthService:
provider=provider, provider=provider,
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
provider_email=provider_email, 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)) + timedelta(seconds=token.get("expires_in", 3600))
if token.get("expires_in") if token.get("expires_in")
else None, else None,
@@ -644,7 +648,9 @@ class OAuthService:
provider=provider, provider=provider,
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
provider_email=email, 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)) + timedelta(seconds=token.get("expires_in", 3600))
if token.get("expires_in") if token.get("expires_in")
else None, else None,

View File

@@ -91,9 +91,7 @@ def spawn_agent(
Returns: Returns:
dict with status, agent_type_id, and project_id dict with status, agent_type_id, and project_id
""" """
logger.info( logger.info(f"Spawning agent of type {agent_type_id} for project {project_id}")
f"Spawning agent of type {agent_type_id} for project {project_id}"
)
# TODO: Implement agent spawning # TODO: Implement agent spawning
# This will involve: # This will involve:
@@ -132,9 +130,7 @@ def terminate_agent(
Returns: Returns:
dict with status and agent_instance_id dict with status and agent_instance_id
""" """
logger.info( logger.info(f"Terminating agent instance {agent_instance_id} with reason: {reason}")
f"Terminating agent instance {agent_instance_id} with reason: {reason}"
)
# TODO: Implement agent termination # TODO: Implement agent termination
# This will involve: # This will involve:

View File

@@ -86,9 +86,7 @@ def commit_changes(
Returns: Returns:
dict with status and project_id dict with status and project_id
""" """
logger.info( logger.info(f"Committing changes for project {project_id}: {message}")
f"Committing changes for project {project_id}: {message}"
)
# TODO: Implement commit operation # TODO: Implement commit operation
# This will involve: # This will involve:
@@ -209,9 +207,7 @@ def push_changes(
Returns: Returns:
dict with status and project_id dict with status and project_id
""" """
logger.info( logger.info(f"Pushing branch {branch} for project {project_id} (force={force})")
f"Pushing branch {branch} for project {project_id} (force={force})"
)
# TODO: Implement push operation # TODO: Implement push operation
# This will involve: # This will involve:

View File

@@ -140,9 +140,7 @@ def sync_project_issues(
Returns: Returns:
dict with status and project_id dict with status and project_id
""" """
logger.info( logger.info(f"Syncing issues for project {project_id} (full={full})")
f"Syncing issues for project {project_id} (full={full})"
)
# TODO: Implement project-specific sync # TODO: Implement project-specific sync
# This will involve: # This will involve:
@@ -180,9 +178,7 @@ def push_issue_to_external(
Returns: Returns:
dict with status, issue_id, and operation dict with status, issue_id, and operation
""" """
logger.info( logger.info(f"Pushing {operation} for issue {issue_id} in project {project_id}")
f"Pushing {operation} for issue {issue_id} in project {project_id}"
)
# TODO: Implement outbound sync # TODO: Implement outbound sync
# This will involve: # This will involve:

View File

@@ -72,9 +72,7 @@ def execute_workflow_step(
Returns: Returns:
dict with status, workflow_id, and transition dict with status, workflow_id, and transition
""" """
logger.info( logger.info(f"Executing transition '{transition}' for workflow {workflow_id}")
f"Executing transition '{transition}' for workflow {workflow_id}"
)
# TODO: Implement workflow transition # TODO: Implement workflow transition
# This will involve: # This will involve:
@@ -196,9 +194,7 @@ def start_story_workflow(
Returns: Returns:
dict with status and story_id dict with status and story_id
""" """
logger.info( logger.info(f"Starting story workflow for story {story_id} in project {project_id}")
f"Starting story workflow for story {story_id} in project {project_id}"
)
# TODO: Implement story workflow initialization # TODO: Implement story workflow initialization
# This will involve: # This will involve: