@@ -59,8 +59,14 @@ class BffProjectsHomeResponse(BaseModel):
5959
6060
6161_HOME_FIELDS = [
62- "id" , "name" , "updated_at" , "language" , "pin_order" , "count(conversations)" ,
62+ "id" ,
63+ "name" ,
64+ "updated_at" ,
65+ "language" ,
66+ "pin_order" ,
67+ "count(conversations)" ,
6368]
69+ _HOME_FIELDS_WITHOUT_PIN_ORDER = [field for field in _HOME_FIELDS if field != "pin_order" ]
6470
6571
6672def _build_project_summary (raw : dict ) -> BffProjectSummary :
@@ -100,13 +106,17 @@ async def get_projects_home(
100106 fields = list (_HOME_FIELDS )
101107 if auth .is_admin :
102108 fields .extend (["directus_user_id.first_name" , "directus_user_id.email" ])
109+ fallback_fields = list (_HOME_FIELDS_WITHOUT_PIN_ORDER )
110+ if auth .is_admin :
111+ fallback_fields .extend (["directus_user_id.first_name" , "directus_user_id.email" ])
103112
104113 # Fetch pinned projects (always, regardless of search)
105114 # Admins see only their own pins; non-admins see all (Directus permissions handle scoping)
106115 pin_filter : dict [str , Any ] = {"pin_order" : {"_nnull" : True }}
107116 if auth .is_admin :
108117 pin_filter ["directus_user_id" ] = {"_eq" : auth .user_id }
109118
119+ supports_pin_order = True
110120 pinned_raw = await run_in_thread_pool (
111121 client .get_items ,
112122 "project" ,
@@ -122,10 +132,12 @@ async def get_projects_home(
122132 if not isinstance (pinned_raw , list ):
123133 logger .warning ("get_items returned non-list for pinned projects: %s" , pinned_raw )
124134 pinned_raw = []
135+ supports_pin_order = False
125136 pinned = [_build_project_summary (p ) for p in pinned_raw ]
126137
127138 # Parse owner: prefix from search string (admin only)
128139 import re
140+
129141 owner_term : Optional [str ] = None
130142 text_search : Optional [str ] = search
131143 if search and auth .is_admin :
@@ -144,8 +156,9 @@ async def get_projects_home(
144156 }
145157
146158 # Build query for paginated project list
159+ list_fields = fields if supports_pin_order else fallback_fields
147160 query : dict = {
148- "fields" : fields ,
161+ "fields" : list_fields ,
149162 "sort" : ["-updated_at" ],
150163 "limit" : limit + 1 ,
151164 "offset" : offset ,
@@ -162,7 +175,17 @@ async def get_projects_home(
162175 )
163176 if not isinstance (projects_raw , list ):
164177 logger .warning ("get_items returned non-list for projects: %s" , projects_raw )
165- projects_raw = []
178+ if supports_pin_order :
179+ supports_pin_order = False
180+ query ["fields" ] = fallback_fields
181+ projects_raw = await run_in_thread_pool (
182+ client .get_items ,
183+ "project" ,
184+ {"query" : query },
185+ )
186+ if not isinstance (projects_raw , list ):
187+ logger .warning ("fallback get_items returned non-list for projects: %s" , projects_raw )
188+ projects_raw = []
166189
167190 has_more = len (projects_raw ) > limit
168191 projects = [_build_project_summary (p ) for p in projects_raw [:limit ]]
@@ -609,9 +632,13 @@ async def create_report(
609632 if not is_scheduled :
610633 # Dispatch background task immediately
611634 task_create_report .send (project_id , report ["id" ], language , body .user_instructions or "" )
612- logger .info (f"Report generation task dispatched for project { project_id } , report { report ['id' ]} " )
635+ logger .info (
636+ f"Report generation task dispatched for project { project_id } , report { report ['id' ]} "
637+ )
613638 else :
614- logger .info (f"Report { report ['id' ]} scheduled for { body .scheduled_at } for project { project_id } " )
639+ logger .info (
640+ f"Report { report ['id' ]} scheduled for { body .scheduled_at } for project { project_id } "
641+ )
615642
616643 return report
617644
@@ -621,6 +648,7 @@ def _extract_report_title(content: Optional[str]) -> Optional[str]:
621648 if not content :
622649 return None
623650 import re
651+
624652 match = re .search (r"^#\s+(.+)$" , content , re .MULTILINE )
625653 return match .group (1 ).strip () if match else None
626654
@@ -642,22 +670,32 @@ async def list_project_reports(
642670 "project_id" : {"_eq" : project_id },
643671 "status" : {"_in" : ["archived" , "published" , "scheduled" , "draft" ]},
644672 },
645- "fields" : ["id" , "status" , "date_created" , "language" , "user_instructions" , "content" , "scheduled_at" ],
673+ "fields" : [
674+ "id" ,
675+ "status" ,
676+ "date_created" ,
677+ "language" ,
678+ "user_instructions" ,
679+ "content" ,
680+ "scheduled_at" ,
681+ ],
646682 "sort" : ["-date_created" ],
647683 }
648684 },
649685 )
650686 result = []
651- for r in (reports or []):
652- result .append ({
653- "id" : r ["id" ],
654- "status" : r .get ("status" ),
655- "date_created" : r .get ("date_created" ),
656- "language" : r .get ("language" ),
657- "user_instructions" : r .get ("user_instructions" ),
658- "scheduled_at" : r .get ("scheduled_at" ),
659- "title" : _extract_report_title (r .get ("content" )),
660- })
687+ for r in reports or []:
688+ result .append (
689+ {
690+ "id" : r ["id" ],
691+ "status" : r .get ("status" ),
692+ "date_created" : r .get ("date_created" ),
693+ "language" : r .get ("language" ),
694+ "user_instructions" : r .get ("user_instructions" ),
695+ "scheduled_at" : r .get ("scheduled_at" ),
696+ "title" : _extract_report_title (r .get ("content" )),
697+ }
698+ )
661699 return result
662700
663701
@@ -856,6 +894,7 @@ async def get_report_views(
856894
857895 # Recent views (last 10 minutes)
858896 from datetime import datetime , timezone , timedelta
897+
859898 ten_mins_ago = (datetime .now (timezone .utc ) - timedelta (minutes = 10 )).isoformat ()
860899 recent_metrics = await run_in_thread_pool (
861900 directus .get_items ,
@@ -976,9 +1015,7 @@ async def _generate_events() -> AsyncIterator[str]:
9761015 # Check if report is already done before subscribing
9771016 from dembrane .directus import directus
9781017
979- report = await run_in_thread_pool (
980- directus .get_item , "project_report" , str (report_id )
981- )
1018+ report = await run_in_thread_pool (directus .get_item , "project_report" , str (report_id ))
9821019 if not report or str (report .get ("project_id" )) != project_id :
9831020 yield f"event: progress\n data: { json .dumps ({'type' : 'failed' , 'message' : 'Report not found' })} \n \n "
9841021 return
0 commit comments