mirror of
https://github.com/omnara-ai/omnara.git
synced 2025-08-12 20:39:09 +03:00
delete agent type (#66)
* delete agent type * more filters --------- Co-authored-by: Kartik Sarangmath <kartiksarangmath@Kartiks-MacBook-Air.local>
This commit is contained in:
@@ -106,10 +106,16 @@ async def create_agent_instance(
|
||||
):
|
||||
"""Create a new instance of a user agent (trigger webhook if applicable)"""
|
||||
|
||||
# Get the user agent
|
||||
# Get the user agent (excluding soft-deleted ones)
|
||||
user_agent = (
|
||||
db.query(UserAgent)
|
||||
.filter(and_(UserAgent.id == agent_id, UserAgent.user_id == current_user.id))
|
||||
.filter(
|
||||
and_(
|
||||
UserAgent.id == agent_id,
|
||||
UserAgent.user_id == current_user.id,
|
||||
UserAgent.is_deleted.is_(False),
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
@@ -61,11 +61,11 @@ def _format_instance(instance: AgentInstance) -> AgentInstanceResponse:
|
||||
def get_all_agent_types_with_instances(
|
||||
db: Session, user_id: UUID
|
||||
) -> list[AgentTypeOverview]:
|
||||
"""Get all user agents with their instances for a specific user - OPTIMIZED"""
|
||||
# Get all user agents for this user with instances in a single query
|
||||
"""Get all non-deleted user agents with their instances for a specific user - OPTIMIZED"""
|
||||
# Get all non-deleted user agents for this user with instances in a single query
|
||||
user_agents = (
|
||||
db.query(UserAgent)
|
||||
.filter(UserAgent.user_id == user_id)
|
||||
.filter(UserAgent.user_id == user_id, UserAgent.is_deleted.is_(False))
|
||||
.options(subqueryload(UserAgent.instances))
|
||||
.all()
|
||||
)
|
||||
@@ -267,7 +267,9 @@ def get_agent_summary(db: Session, user_id: UUID) -> dict:
|
||||
)
|
||||
.join(AgentInstance, AgentInstance.user_agent_id == UserAgent.id)
|
||||
.filter(
|
||||
UserAgent.user_id == user_id, AgentInstance.status != AgentStatus.DELETED
|
||||
UserAgent.user_id == user_id,
|
||||
UserAgent.is_deleted.is_(False),
|
||||
AgentInstance.status != AgentStatus.DELETED,
|
||||
)
|
||||
.group_by(UserAgent.id, UserAgent.name, AgentInstance.status)
|
||||
.all()
|
||||
@@ -304,7 +306,11 @@ def get_agent_type_instances(
|
||||
|
||||
user_agent = (
|
||||
db.query(UserAgent)
|
||||
.filter(UserAgent.id == agent_type_id, UserAgent.user_id == user_id)
|
||||
.filter(
|
||||
UserAgent.id == agent_type_id,
|
||||
UserAgent.user_id == user_id,
|
||||
UserAgent.is_deleted.is_(False),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not user_agent:
|
||||
|
||||
@@ -29,10 +29,16 @@ def create_user_agent(
|
||||
) -> dict | None:
|
||||
"""Create a new user agent configuration"""
|
||||
|
||||
# Check if agent with same name already exists for this user
|
||||
# Check if non-deleted agent with same name already exists for this user
|
||||
existing = (
|
||||
db.query(UserAgent)
|
||||
.filter(and_(UserAgent.user_id == user_id, UserAgent.name == request.name))
|
||||
.filter(
|
||||
and_(
|
||||
UserAgent.user_id == user_id,
|
||||
UserAgent.name == request.name,
|
||||
UserAgent.is_deleted.is_(False),
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -55,9 +61,13 @@ def create_user_agent(
|
||||
|
||||
|
||||
def get_user_agents(db: Session, user_id: UUID) -> list[dict]:
|
||||
"""Get all user agents for a specific user"""
|
||||
"""Get all non-deleted user agents for a specific user"""
|
||||
|
||||
user_agents = db.query(UserAgent).filter(UserAgent.user_id == user_id).all()
|
||||
user_agents = (
|
||||
db.query(UserAgent)
|
||||
.filter(and_(UserAgent.user_id == user_id, UserAgent.is_deleted.is_(False)))
|
||||
.all()
|
||||
)
|
||||
|
||||
return [_format_user_agent(agent, db) for agent in user_agents]
|
||||
|
||||
@@ -69,7 +79,13 @@ def update_user_agent(
|
||||
|
||||
user_agent = (
|
||||
db.query(UserAgent)
|
||||
.filter(and_(UserAgent.id == agent_id, UserAgent.user_id == user_id))
|
||||
.filter(
|
||||
and_(
|
||||
UserAgent.id == agent_id,
|
||||
UserAgent.user_id == user_id,
|
||||
UserAgent.is_deleted.is_(False),
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -266,10 +282,16 @@ async def trigger_webhook_agent(
|
||||
def get_user_agent_instances(db: Session, agent_id: UUID, user_id: UUID) -> list | None:
|
||||
"""Get all instances for a specific user agent"""
|
||||
|
||||
# Verify the user agent exists and belongs to the user
|
||||
# Verify the user agent exists, belongs to the user, and is not deleted
|
||||
user_agent = (
|
||||
db.query(UserAgent)
|
||||
.filter(and_(UserAgent.id == agent_id, UserAgent.user_id == user_id))
|
||||
.filter(
|
||||
and_(
|
||||
UserAgent.id == agent_id,
|
||||
UserAgent.user_id == user_id,
|
||||
UserAgent.is_deleted.is_(False),
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -293,12 +315,18 @@ def get_user_agent_instances(db: Session, agent_id: UUID, user_id: UUID) -> list
|
||||
|
||||
|
||||
def delete_user_agent(db: Session, agent_id: UUID, user_id: UUID) -> bool:
|
||||
"""Delete a user agent and all its associated instances and related data"""
|
||||
"""Soft delete a user agent and mark its instances as deleted, while removing messages"""
|
||||
|
||||
# First verify the user agent exists and belongs to the user
|
||||
# First verify the user agent exists, belongs to the user, and is not already deleted
|
||||
user_agent = (
|
||||
db.query(UserAgent)
|
||||
.filter(and_(UserAgent.id == agent_id, UserAgent.user_id == user_id))
|
||||
.filter(
|
||||
and_(
|
||||
UserAgent.id == agent_id,
|
||||
UserAgent.user_id == user_id,
|
||||
UserAgent.is_deleted.is_(False),
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -310,16 +338,19 @@ def delete_user_agent(db: Session, agent_id: UUID, user_id: UUID) -> bool:
|
||||
db.query(AgentInstance).filter(AgentInstance.user_agent_id == agent_id).all()
|
||||
)
|
||||
|
||||
# For each agent instance, delete all related data
|
||||
# For each agent instance, delete all messages (for privacy/storage)
|
||||
for instance in agent_instances:
|
||||
# Delete messages
|
||||
db.query(Message).filter(Message.agent_instance_id == instance.id).delete()
|
||||
|
||||
# Delete all agent instances
|
||||
db.query(AgentInstance).filter(AgentInstance.user_agent_id == agent_id).delete()
|
||||
# Mark all agent instances as DELETED
|
||||
db.query(AgentInstance).filter(AgentInstance.user_agent_id == agent_id).update(
|
||||
{"status": AgentStatus.DELETED}
|
||||
)
|
||||
|
||||
# Soft delete the user agent
|
||||
user_agent.is_deleted = True
|
||||
user_agent.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
# Delete the user agent
|
||||
db.delete(user_agent)
|
||||
db.commit()
|
||||
|
||||
return True
|
||||
|
||||
@@ -20,13 +20,18 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_or_get_user_agent(db: Session, name: str, user_id: str) -> UserAgent:
|
||||
"""Create or get a user agent by name for a specific user"""
|
||||
"""Create or get a non-deleted user agent by name for a specific user"""
|
||||
# Normalize name to lowercase for consistent storage
|
||||
normalized_name = name.lower()
|
||||
|
||||
# Only look for non-deleted user agents
|
||||
user_agent = (
|
||||
db.query(UserAgent)
|
||||
.filter(UserAgent.name == normalized_name, UserAgent.user_id == UUID(user_id))
|
||||
.filter(
|
||||
UserAgent.name == normalized_name,
|
||||
UserAgent.user_id == UUID(user_id),
|
||||
UserAgent.is_deleted.is_(False),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not user_agent:
|
||||
@@ -34,6 +39,7 @@ def create_or_get_user_agent(db: Session, name: str, user_id: str) -> UserAgent:
|
||||
name=normalized_name,
|
||||
user_id=UUID(user_id),
|
||||
is_active=True,
|
||||
is_deleted=False, # Explicitly set to False for new agents
|
||||
)
|
||||
db.add(user_agent)
|
||||
db.flush() # Flush to get the user_agent ID
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Add soft delete to user agents
|
||||
|
||||
Revision ID: 9f61865b8ba8
|
||||
Revises: 2e2f1b18e835
|
||||
Create Date: 2025-08-10 22:22:28.432623
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "9f61865b8ba8"
|
||||
down_revision: Union[str, None] = "2e2f1b18e835"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
# Add column as nullable first with server default
|
||||
op.add_column(
|
||||
"user_agents",
|
||||
sa.Column(
|
||||
"is_deleted", sa.Boolean(), server_default=sa.text("FALSE"), nullable=True
|
||||
),
|
||||
)
|
||||
|
||||
# Set default value for existing rows (in case any are NULL)
|
||||
op.execute("UPDATE user_agents SET is_deleted = FALSE WHERE is_deleted IS NULL")
|
||||
|
||||
# Now make the column NOT NULL
|
||||
op.alter_column(
|
||||
"user_agents", "is_deleted", nullable=False, server_default=sa.text("FALSE")
|
||||
)
|
||||
|
||||
op.drop_constraint(
|
||||
op.f("uq_user_agents_user_id_name"), "user_agents", type_="unique"
|
||||
)
|
||||
op.create_index(
|
||||
"uq_user_agents_user_id_name",
|
||||
"user_agents",
|
||||
["user_id", "name"],
|
||||
unique=True,
|
||||
postgresql_where=sa.text("is_deleted = FALSE"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(
|
||||
"uq_user_agents_user_id_name",
|
||||
table_name="user_agents",
|
||||
postgresql_where=sa.text("is_deleted = FALSE"),
|
||||
)
|
||||
op.create_unique_constraint(
|
||||
op.f("uq_user_agents_user_id_name"),
|
||||
"user_agents",
|
||||
["user_id", "name"],
|
||||
postgresql_nulls_not_distinct=False,
|
||||
)
|
||||
op.drop_column("user_agents", "is_deleted")
|
||||
# ### end Alembic commands ###
|
||||
@@ -2,7 +2,7 @@ from datetime import datetime, timezone
|
||||
from uuid import UUID, uuid4
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import ForeignKey, Index, String, Text, UniqueConstraint
|
||||
from sqlalchemy import ForeignKey, Index, String, Text, text
|
||||
from sqlalchemy.dialects.postgresql import UUID as PostgresUUID, JSONB
|
||||
from sqlalchemy.orm import (
|
||||
DeclarativeBase, # type: ignore[attr-defined]
|
||||
@@ -77,7 +77,14 @@ class User(Base):
|
||||
class UserAgent(Base):
|
||||
__tablename__ = "user_agents"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("user_id", "name", name="uq_user_agents_user_id_name"),
|
||||
# Partial unique index: only enforce uniqueness for non-deleted agents
|
||||
Index(
|
||||
"uq_user_agents_user_id_name",
|
||||
"user_id",
|
||||
"name",
|
||||
unique=True,
|
||||
postgresql_where=text("is_deleted = FALSE"),
|
||||
),
|
||||
Index("ix_user_agents_user_id", "user_id"),
|
||||
)
|
||||
|
||||
@@ -91,6 +98,7 @@ class UserAgent(Base):
|
||||
webhook_url: Mapped[str | None] = mapped_column(Text, default=None)
|
||||
webhook_api_key: Mapped[str | None] = mapped_column(Text, default=None) # Encrypted
|
||||
is_active: Mapped[bool] = mapped_column(default=True)
|
||||
is_deleted: Mapped[bool] = mapped_column(default=False) # Soft delete flag
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
default=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user