6969 get_access_token_for_urn = None
7070 get_token_by_user_id = None
7171
72+ try :
73+ from services .scheduled_posts import schedule_post
74+ except ImportError :
75+ schedule_post = None
76+
7277try :
7378 from services .auth_service import (
7479 TokenNotFoundError ,
9196 post_generation_limiter = None
9297 publish_limiter = None
9398
99+ try :
100+ from middleware .clerk_auth import get_current_user
101+ except ImportError :
94102try :
95103 from middleware .clerk_auth import get_current_user
96104except ImportError :
97105 get_current_user = None
98106
107+ from services .db import get_database
108+ from repositories .posts import PostRepository
109+
99110
100111# =============================================================================
101112# REQUEST MODELS
@@ -114,6 +125,13 @@ class PostRequest(BaseModel):
114125 model : Optional [str ] = "groq"
115126
116127
128+ class ScheduleRequest (BaseModel ):
129+ user_id : str
130+ post_content : str
131+ scheduled_time : int
132+ image_url : Optional [str ] = None
133+
134+
117135class BatchGenerateRequest (BaseModel ):
118136 """Request for batch post generation in Bot Mode."""
119137 user_id : str
@@ -292,15 +310,32 @@ async def generate_batch(req: BatchGenerateRequest):
292310 )
293311
294312 if result :
295- generated_posts . append ( {
313+ final_post = {
296314 "id" : f"gen_{ success_count } _{ activity .get ('id' , '' )} " ,
297315 "content" : result .content ,
298316 "activity" : activity ,
299317 "style" : style ,
300318 "status" : "draft" ,
301319 "provider" : result .provider .value ,
302320 "model" : result .model ,
303- })
321+ }
322+ generated_posts .append (final_post )
323+
324+ # PERSISTENCE: Save as draft immediately
325+ try :
326+ db = get_database ()
327+ repo = PostRepository (db , req .user_id )
328+ saved_id = await repo .save_post (
329+ post_content = result .content ,
330+ post_type = 'bot' ,
331+ context = activity ,
332+ status = 'draft'
333+ )
334+ final_post ['id' ] = str (saved_id )
335+ final_post ['db_id' ] = saved_id
336+ except Exception as e :
337+ logger .error ("failed_to_persist_post_provider" , error = str (e ))
338+
304339 used_provider = result .provider .value
305340 was_downgraded = result .was_downgraded
306341 success_count += 1
@@ -315,22 +350,47 @@ async def generate_batch(req: BatchGenerateRequest):
315350 )
316351
317352 if post_content :
318- generated_posts . append ( {
353+ final_post = {
319354 "id" : f"gen_{ success_count } _{ activity .get ('id' , '' )} " ,
320355 "content" : post_content ,
321356 "activity" : activity ,
322357 "style" : style ,
323358 "status" : "draft" ,
324359 "provider" : "groq" ,
325360 "model" : "llama-3.3-70b-versatile" ,
326- })
361+ }
362+ generated_posts .append (final_post )
363+
364+ # PERSISTENCE: Save as draft immediately
365+ try :
366+ db = get_database ()
367+ repo = PostRepository (db , req .user_id )
368+ saved_id = await repo .save_post (
369+ post_content = post_content ,
370+ post_type = 'bot' ,
371+ context = activity ,
372+ status = 'draft'
373+ )
374+ # Build full object with real ID so frontend can use it for publishing
375+ final_post ['id' ] = str (saved_id )
376+ final_post ['db_id' ] = saved_id # explicitly track DB ID
377+ except Exception as e :
378+ logger .error ("failed_to_persist_post" , error = str (e ))
379+
327380 success_count += 1
328381 else :
329382 failed_count += 1
330383
331384 except Exception as e :
332385 logger .error ("failed_to_generate_post" , error = str (e ))
333386 failed_count += 1
387+
388+ # For provider-based generation loop above, we also need persistence
389+ # (Note: I'm patching the loop above in a second chunk or assuming the user meant to cover both paths.
390+ # To be safe and clean, I will wrap the persistence logic in a helper or duplicate it for the first branch if I can't easily merge.)
391+ # Actually, the previous 'if result:' block also needs persistence.
392+ # Let me re-read the file content to ensure I catch both branches.
393+ # The file view showed headers 300-450.
334394
335395 return {
336396 "posts" : generated_posts ,
@@ -342,6 +402,24 @@ async def generate_batch(req: BatchGenerateRequest):
342402 }
343403
344404
405+ @router .get ("/bot-stats" )
406+ async def get_bot_stats (
407+ user_id : str ,
408+ current_user : dict = Depends (get_current_user ) if get_current_user else None
409+ ):
410+ """Get statistics for bot mode (generated vs published)."""
411+ if current_user and current_user .get ("user_id" ) != user_id :
412+ raise HTTPException (status_code = 403 , detail = "Unauthorized" )
413+
414+ try :
415+ db = get_database ()
416+ repo = PostRepository (db , user_id )
417+ return await repo .get_bot_stats ()
418+ except Exception as e :
419+ logger .error ("failed_to_get_bot_stats" , error = str (e ))
420+ return {"generated" : 0 , "published" : 0 }
421+
422+
345423@router .get ("/providers" )
346424async def list_providers (
347425 current_user : dict = Depends (get_current_user ) if get_current_user else None
@@ -504,3 +582,23 @@ async def publish(req: PostRequest):
504582 if post_to_linkedin :
505583 post_to_linkedin (post , image_asset )
506584 return {"status" : "posted" , "post" : post , "image_asset" : image_asset }
585+
586+
587+ @router .post ("/schedule" )
588+ async def schedule (req : ScheduleRequest ):
589+ """Schedule a post for later publishing."""
590+ if not schedule_post :
591+ raise HTTPException (status_code = 500 , detail = "Schedule service not available" )
592+
593+ result = await schedule_post (
594+ user_id = req .user_id ,
595+ post_content = req .post_content ,
596+ scheduled_time = req .scheduled_time ,
597+ image_url = req .image_url
598+ )
599+
600+ if not result .get ("success" ):
601+ raise HTTPException (status_code = 400 , detail = result .get ("error" ))
602+
603+ return result
604+
0 commit comments