From 92c0474df4edce3dfdb99652e43d3460211f6e37 Mon Sep 17 00:00:00 2001 From: Aditya Vaish Date: Thu, 16 Oct 2025 10:34:08 +0530 Subject: [PATCH] bugs fixed --- .../app/connectors/clickup_connector.py | 83 ++++++++++++++----- surfsense_backend/app/db.py | 7 ++ .../app/routes/scheduler_routes.py | 20 ++++- .../app/schemas/connector_schedule.py | 1 + 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/surfsense_backend/app/connectors/clickup_connector.py b/surfsense_backend/app/connectors/clickup_connector.py index 6ba5cc64c..932e20855 100644 --- a/surfsense_backend/app/connectors/clickup_connector.py +++ b/surfsense_backend/app/connectors/clickup_connector.py @@ -5,7 +5,7 @@ A module for retrieving data from ClickUp. Allows fetching tasks from workspaces and lists. """ -from datetime import datetime +from datetime import datetime, timezone from typing import Any import requests @@ -180,40 +180,79 @@ class ClickUpConnector: start_timestamp = int(start_datetime.timestamp() * 1000) end_timestamp = int(end_datetime.timestamp() * 1000) - params = { + # Base parameters for both requests + base_params = { "page": 0, "order_by": "created", "reverse": "true", "subtasks": "true", "include_closed": str(include_closed).lower(), - # Date filtering - filter by both created and updated dates - "date_created_gt": start_timestamp, - "date_created_lt": end_timestamp, - "date_updated_gt": start_timestamp, - "date_updated_lt": end_timestamp, } - all_tasks = [] - page = 0 + # Request 1: Filter by creation date + created_params = base_params.copy() + created_params.update({ + "date_created_gt": start_timestamp, + "date_created_lt": end_timestamp, + }) - while True: - params["page"] = page - result = self.make_api_request(f"team/{workspace_id}/task", params) + # Request 2: Filter by update date + updated_params = base_params.copy() + updated_params.update({ + "date_updated_gt": start_timestamp, + "date_updated_lt": end_timestamp, + }) - if not isinstance(result, dict) or "tasks" not in result: - return [], "Invalid response from ClickUp API" + def fetch_tasks_with_params(params): + """Helper function to fetch all tasks for given parameters""" + all_tasks = [] + page = 0 - tasks = result["tasks"] - if not tasks: - break + while True: + params["page"] = page + result = self.make_api_request(f"team/{workspace_id}/task", params) - all_tasks.extend(tasks) + if not isinstance(result, dict) or "tasks" not in result: + return [], "Invalid response from ClickUp API" - # Check if there are more pages - if len(tasks) < 100: # ClickUp returns max 100 tasks per page - break + tasks = result["tasks"] + if not tasks: + break - page += 1 + all_tasks.extend(tasks) + + # Check if there are more pages + if len(tasks) < 100: # ClickUp returns max 100 tasks per page + break + + page += 1 + + return all_tasks, None + + # Fetch tasks created in date range + created_tasks, error = fetch_tasks_with_params(created_params) + if error: + return [], error + + # Fetch tasks updated in date range + updated_tasks, error = fetch_tasks_with_params(updated_params) + if error: + return [], error + + # Merge and deduplicate tasks by ID + # Use a dictionary to store unique tasks (ID as key) + unique_tasks = {} + + # Add created tasks + for task in created_tasks: + unique_tasks[task["id"]] = task + + # Add updated tasks (will overwrite duplicates, keeping the latest) + for task in updated_tasks: + unique_tasks[task["id"]] = task + + # Convert back to list + all_tasks = list(unique_tasks.values()) if not all_tasks: return [], "No tasks found in the specified date range." diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 648a1434a..f0244b984 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -336,6 +336,8 @@ class ConnectorSchedule(BaseModel, TimestampMixin): ) schedule_type = Column(SQLAlchemyEnum(ScheduleType), nullable=False) cron_expression = Column(String(100), nullable=True) + # Timezone for interpreting daily_time and weekly_time (IANA zone name) + timezone = Column(String(50), nullable=False, default="UTC") # Optional schedule config fields (persist user selections) daily_time = Column(Time(timezone=False), nullable=True) weekly_day = Column(Integer, nullable=True) # 0=Mon .. 6=Sun @@ -445,6 +447,11 @@ if config.AUTH_TYPE == "GOOGLE": "OAuthAccount", lazy="joined" ) search_spaces = relationship("SearchSpace", back_populates="user") + search_source_connectors = relationship( + "SearchSourceConnector", + back_populates="user", + cascade="all, delete-orphan", + ) search_space_preferences = relationship( "UserSearchSpacePreference", back_populates="user", diff --git a/surfsense_backend/app/routes/scheduler_routes.py b/surfsense_backend/app/routes/scheduler_routes.py index 0821de37b..03a43b043 100644 --- a/surfsense_backend/app/routes/scheduler_routes.py +++ b/surfsense_backend/app/routes/scheduler_routes.py @@ -10,10 +10,11 @@ from sqlalchemy import select, Integer from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from app.db import get_async_session, Log +from app.db import get_async_session, Log, ConnectorSchedule, SearchSourceConnector from app.schemas import ConnectorScheduleRead from app.services.connector_scheduler_service import get_scheduler from app.users import User, current_active_user +from app.utils.check_ownership import check_ownership logger = logging.getLogger(__name__) @@ -49,6 +50,7 @@ async def get_scheduler_status( async def force_execute_schedule( schedule_id: int, background_tasks: BackgroundTasks, + session: AsyncSession = Depends(get_async_session), user: User = Depends(current_active_user), ): """ @@ -58,6 +60,20 @@ async def force_execute_schedule( the connector sync for the specified schedule. """ try: + # First, verify ownership of the schedule + # Load the schedule and verify the user owns the associated connector + result = await session.execute( + select(ConnectorSchedule).filter(ConnectorSchedule.id == schedule_id) + ) + schedule = result.scalars().first() + + if not schedule: + raise HTTPException(status_code=404, detail="Schedule not found") + + # Verify schedule's connector belongs to user + await check_ownership(session, SearchSourceConnector, schedule.connector_id, user) + + # Only after ownership is confirmed, proceed with scheduling scheduler = await get_scheduler() # Add the force execution to background tasks @@ -72,6 +88,8 @@ async def force_execute_schedule( "schedule_id": schedule_id } + except HTTPException: + raise except Exception as e: logger.error(f"Error force executing schedule {schedule_id}: {e}") raise HTTPException( diff --git a/surfsense_backend/app/schemas/connector_schedule.py b/surfsense_backend/app/schemas/connector_schedule.py index 9467bfdae..cab3faa9b 100644 --- a/surfsense_backend/app/schemas/connector_schedule.py +++ b/surfsense_backend/app/schemas/connector_schedule.py @@ -14,6 +14,7 @@ class ConnectorScheduleBase(BaseModel): schedule_type: ScheduleType cron_expression: str | None = None is_active: bool = True + timezone: str = "UTC" # IANA timezone name for interpreting daily_time and weekly_time # Enhanced time selection options daily_time: Optional[time] = None # For DAILY schedules (default: 02:00)