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)"""
|
"""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 = (
|
user_agent = (
|
||||||
db.query(UserAgent)
|
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()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ def _format_instance(instance: AgentInstance) -> AgentInstanceResponse:
|
|||||||
def get_all_agent_types_with_instances(
|
def get_all_agent_types_with_instances(
|
||||||
db: Session, user_id: UUID
|
db: Session, user_id: UUID
|
||||||
) -> list[AgentTypeOverview]:
|
) -> list[AgentTypeOverview]:
|
||||||
"""Get all user agents with their instances for a specific user - OPTIMIZED"""
|
"""Get all non-deleted 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 for this user with instances in a single query
|
||||||
user_agents = (
|
user_agents = (
|
||||||
db.query(UserAgent)
|
db.query(UserAgent)
|
||||||
.filter(UserAgent.user_id == user_id)
|
.filter(UserAgent.user_id == user_id, UserAgent.is_deleted.is_(False))
|
||||||
.options(subqueryload(UserAgent.instances))
|
.options(subqueryload(UserAgent.instances))
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
@@ -267,7 +267,9 @@ def get_agent_summary(db: Session, user_id: UUID) -> dict:
|
|||||||
)
|
)
|
||||||
.join(AgentInstance, AgentInstance.user_agent_id == UserAgent.id)
|
.join(AgentInstance, AgentInstance.user_agent_id == UserAgent.id)
|
||||||
.filter(
|
.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)
|
.group_by(UserAgent.id, UserAgent.name, AgentInstance.status)
|
||||||
.all()
|
.all()
|
||||||
@@ -304,7 +306,11 @@ def get_agent_type_instances(
|
|||||||
|
|
||||||
user_agent = (
|
user_agent = (
|
||||||
db.query(UserAgent)
|
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()
|
.first()
|
||||||
)
|
)
|
||||||
if not user_agent:
|
if not user_agent:
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ def create_user_agent(
|
|||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Create a new user agent configuration"""
|
"""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 = (
|
existing = (
|
||||||
db.query(UserAgent)
|
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()
|
.first()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,9 +61,13 @@ def create_user_agent(
|
|||||||
|
|
||||||
|
|
||||||
def get_user_agents(db: Session, user_id: UUID) -> list[dict]:
|
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]
|
return [_format_user_agent(agent, db) for agent in user_agents]
|
||||||
|
|
||||||
@@ -69,7 +79,13 @@ def update_user_agent(
|
|||||||
|
|
||||||
user_agent = (
|
user_agent = (
|
||||||
db.query(UserAgent)
|
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()
|
.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:
|
def get_user_agent_instances(db: Session, agent_id: UUID, user_id: UUID) -> list | None:
|
||||||
"""Get all instances for a specific user agent"""
|
"""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 = (
|
user_agent = (
|
||||||
db.query(UserAgent)
|
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()
|
.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:
|
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 = (
|
user_agent = (
|
||||||
db.query(UserAgent)
|
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()
|
.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()
|
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:
|
for instance in agent_instances:
|
||||||
# Delete messages
|
|
||||||
db.query(Message).filter(Message.agent_instance_id == instance.id).delete()
|
db.query(Message).filter(Message.agent_instance_id == instance.id).delete()
|
||||||
|
|
||||||
# Delete all agent instances
|
# Mark all agent instances as DELETED
|
||||||
db.query(AgentInstance).filter(AgentInstance.user_agent_id == agent_id).delete()
|
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()
|
db.commit()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -20,13 +20,18 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def create_or_get_user_agent(db: Session, name: str, user_id: str) -> UserAgent:
|
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
|
# Normalize name to lowercase for consistent storage
|
||||||
normalized_name = name.lower()
|
normalized_name = name.lower()
|
||||||
|
|
||||||
|
# Only look for non-deleted user agents
|
||||||
user_agent = (
|
user_agent = (
|
||||||
db.query(UserAgent)
|
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()
|
.first()
|
||||||
)
|
)
|
||||||
if not user_agent:
|
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,
|
name=normalized_name,
|
||||||
user_id=UUID(user_id),
|
user_id=UUID(user_id),
|
||||||
is_active=True,
|
is_active=True,
|
||||||
|
is_deleted=False, # Explicitly set to False for new agents
|
||||||
)
|
)
|
||||||
db.add(user_agent)
|
db.add(user_agent)
|
||||||
db.flush() # Flush to get the user_agent ID
|
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 uuid import UUID, uuid4
|
||||||
from typing import TYPE_CHECKING
|
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.dialects.postgresql import UUID as PostgresUUID, JSONB
|
||||||
from sqlalchemy.orm import (
|
from sqlalchemy.orm import (
|
||||||
DeclarativeBase, # type: ignore[attr-defined]
|
DeclarativeBase, # type: ignore[attr-defined]
|
||||||
@@ -77,7 +77,14 @@ class User(Base):
|
|||||||
class UserAgent(Base):
|
class UserAgent(Base):
|
||||||
__tablename__ = "user_agents"
|
__tablename__ = "user_agents"
|
||||||
__table_args__ = (
|
__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"),
|
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_url: Mapped[str | None] = mapped_column(Text, default=None)
|
||||||
webhook_api_key: Mapped[str | None] = mapped_column(Text, default=None) # Encrypted
|
webhook_api_key: Mapped[str | None] = mapped_column(Text, default=None) # Encrypted
|
||||||
is_active: Mapped[bool] = mapped_column(default=True)
|
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(
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
default=lambda: datetime.now(timezone.utc)
|
default=lambda: datetime.now(timezone.utc)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user