
    5iim                    n   U d Z ddlmZ ddlZddlZddlmZmZ ddlmZ ddl	m
Z
mZ ddlZddlZdaded<   ej                   j#                  d	      Zes ed
      dBdZdCdZdBdZddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dDdZdEdZ	 dF	 	 	 	 	 	 	 dGdZdHdZdIdZdJdZdddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dKdZdLdZdMdZ	 dN	 	 	 	 	 	 	 	 	 dOdZ dPd Z!	 	 	 	 	 	 dQd!Z"	 	 	 	 	 	 dRd"Z#dSd#Z$dddd$ddd%	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dTd&Z%dPd'Z&dLd(Z'	 	 	 	 	 	 dQd)Z(dd*ddd+ddd,	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dUd-Z)dPd.Z*dVd/Z+dddddddddddddd0d1	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dWd2Z,dXd3Z-dYd4Z.dZd5Z/d[d6Z0dJd7Z1d\d8Z2d]d9Z3d^d_d:Z4d`d;Z5dad<Z6dbd=Z7dJd>Z8dcd?Z9ddd@Z:dedAZ;y)fu  
PostgreSQL data access layer for the Video Production Pipeline.

Uses asyncpg for high-performance async database operations.
Supports storing assets (images, videos, audios) as BYTEA.

Tables:
  projects   – one row per pipeline run
  images     – one row per generated image (with optional BYTEA data)
  videos     – one row per generated video (with optional BYTEA data)
  audios     – one row per generated audio (with optional BYTEA data)
  characters – character templates
    )annotationsN)datetimetimezone)Path)AnyOptionalzOptional[asyncpg.Pool]_poolDATABASE_URLzDATABASE_URL environment variable is required. Set it in .env or docker-compose.yml. Example: postgresql://user:password@localhost:5432/dbnamec                 d  K   t         yt        } t        j                  | ddd       d{   a t         j	                         4 d{   }|j                  d       d{    |j                  d       d{    ddd      d{    y7 d7 I7 27 7 # 1 d{  7  sw Y   yxY ww)z@Initialize the database connection pool and ensure tables exist.N   
   <   )min_sizemax_sizecommand_timeouta  
            CREATE TABLE IF NOT EXISTS grok_accounts (
                id SERIAL PRIMARY KEY,
                label VARCHAR(100) NOT NULL DEFAULT '',
                sso_token TEXT NOT NULL,
                sso_rw_token TEXT NOT NULL,
                user_id VARCHAR(100) NOT NULL,
                is_active BOOLEAN NOT NULL DEFAULT TRUE,
                last_used_at TIMESTAMPTZ,
                usage_count INTEGER NOT NULL DEFAULT 0,
                daily_usage_count INTEGER NOT NULL DEFAULT 0,
                daily_usage_reset_at DATE,
                created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
                updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
            )
        z
            CREATE INDEX IF NOT EXISTS idx_grok_accounts_rotation
            ON grok_accounts(is_active, last_used_at NULLS FIRST, usage_count ASC)
        )r	   r
   asyncpgcreate_poolacquireexecute)database_urlconns     8/root/.openclaw/workspace/visionaryfx/web/database_pg.pyinit_dbr   &   s      L%%	 E }}  $ll   	 	  ll   	 	#  	 	#   s{   *B0BB0	B
B0B"B#B;B<B B0BB0B0BBB0B-!B$"B-)B0c                 J   K   t         t                d{    t         S 7 
w)z"Return the shared connection pool.N)r	   r        r   get_poolr   M   s     }iL 	s   #!#c                 Z   K   t         t         j                          d{    da yy7 w)z#Close the database connection pool.N)r	   closer   r   r   close_dbr    T   s*      kkm s   +)	+runningplantiktok )statusmodeplatform	characterstory_previewtitlec                @  K   t                d {   }|j                         4 d {   }|j                  d| ||||||t        j                  t
        j                        	       d {    d d d       d {    y 7 p7 Y7 7 # 1 d {  7  sw Y   y xY ww)NzINSERT INTO projects (id, status, mode, platform, character,
                                     story_preview, title, created_at)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8)r   r   r   r   nowr   utc)	idr%   r&   r'   r(   r)   r*   poolr   s	            r   insert_projectr1   a   s      D||~ 
 
ll: LL&
 	
 	

 
 
 
	

 
 
 
sf   BBBBB>B	+B,B	0B;B<BBB	B	BBBBc                   K   t                d {   }|j                         4 d {   }|j                  d||        d {    d d d       d {    y 7 I7 27 7 # 1 d {  7  sw Y   y xY ww)Nz-UPDATE projects SET status = $1 WHERE id = $2r   r   r   )
project_idr%   r0   r   s       r   update_project_statusr5   |   sx     D||~ 
 
ll;VZ
 	
 	

 
 
 
	

 
 
 
sf   A7AA7AA7A"AA"	A7A A7A7A" A7"A4(A+)A40A7c                  K   t                d {   }|j                         4 d {   }|r0|j                  dt        j                  |      ||        d {    n.|j                  dt        j                  |      |        d {    d d d       d {    y 7 7 w7 H7 7 # 1 d {  7  sw Y   y xY ww)Nz>UPDATE projects SET result_json = $1, title = $2 WHERE id = $3z2UPDATE projects SET result_json = $1 WHERE id = $2)r   r   r   jsondumps)r4   resultr*   r0   r   s        r   update_project_resultr:      s      D||~  ,,P

6"	   ,,D

6"        sx   B>BB>B!B>-B)B#.B)	B%
B)B>B'B>!B>#B)%B)'B>)B;/B20B;7B>c                H  K   t                d {   }|j                         4 d {   }|j                  d|        d {   }|	 d d d       d {    y t        |      }|j	                  d      r8t        |d   t              rt        j                  |d         |d<   n|d   |d<   nd |d<   |d= t        |d         |d<   |j	                  d      r|d   j                         |d<   |cd d d       d {    S 7 7 7 7 7 # 1 d {  7  sw Y   y xY ww)Nz$SELECT * FROM projects WHERE id = $1result_jsonr9   r/   
created_at)
r   r   fetchrowdictget
isinstancestrr7   loads	isoformat)r4   r0   r   rowprojs        r   get_projectrG      s    D||~  MM"H*UU;   Cy88M"$}-s3!%D,?!@X!%m!4X!DNd_T
88L!!%l!3!=!=!?D'   U    s   D"DD"DD"DDDD"D	D"BD1D"=D>D"D"D	D"D"DDDD"c                   K   t                d {   }|j                         4 d {   }|j                  d|        d {   }|dk(  cd d d       d {    S 7 M7 67 7 # 1 d {  7  sw Y   y xY ww)Nz"DELETE FROM projects WHERE id = $1DELETE 1r3   )r4   r0   r   r9   s       r   delete_projectrJ      su     D||~ $ $||$H*UU#$ $ $ $U$ $ $ $f   A;AA;A A;A&A"A&A;A$A; A;"A&$A;&A8,A/-A84A;c                   K   t                d {   } | j                         4 d {   }|j                  d       d {   }g }|D ]V  }t        |      }t	        |d         |d<   |j                  d      r|d   j                         |d<   |j                  |       X |cd d d       d {    S 7 7 7 x7 # 1 d {  7  sw Y   y xY ww)NzSELECT id, status, mode, platform, character,
                      story_preview, title, created_at
               FROM projects ORDER BY created_at DESCr/   r=   r   r   fetchr?   rB   r@   rD   append)r0   r   rowsr9   rds         r   list_projectsrS      s     D||~  ZZ9
 

  	AQA!D'lAdGuu\""#L/";";"=,MM!	    
   sg   CB7CB9CB?B;A"B?%C1B=2C9C;B?=C?CCCC	image/png	generated)image_prompt	file_path
image_data	mime_typer%   c                f  K   t                d {   }t        j                  t        j                        }|rt        |      nd }	|j                         4 d {   }
|
j                  d| ||||||	|||       d {    d d d       d {    y 7 7 :7 7 # 1 d {  7  sw Y   y xY ww)NaB  INSERT INTO images (project_id, scene_number, image_prompt,
                                   file_path, image_data, mime_type, file_size_bytes,
                                   status, created_at, updated_at)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
               ON CONFLICT(project_id, scene_number)
               DO UPDATE SET image_prompt = EXCLUDED.image_prompt,
                             file_path = EXCLUDED.file_path,
                             image_data = COALESCE(EXCLUDED.image_data, images.image_data),
                             mime_type = EXCLUDED.mime_type,
                             file_size_bytes = COALESCE(EXCLUDED.file_size_bytes, images.file_size_bytes),
                             status = EXCLUDED.status,
                             updated_at = EXCLUDED.updated_atr   r   r-   r   r.   lenr   r   )r4   scene_numberrV   rW   rX   rY   r%   r0   r-   	file_sizer   s              r   upsert_imager_      s      D
,,x||
$C#-J4I||~ 
 
llA -
 	
 	

 
 
	 
	

 
 
 
sj   B1BA
B1BB1B>B?BB1BB1B1BB1B."B%#B.*B1c                8  K   t                d {   }|j                         4 d {   }|j                  d|t        j                  t
        j                        | |       d {    d d d       d {    y 7 l7 U7 7 # 1 d {  7  sw Y   y xY ww)NziUPDATE images SET status = $1, updated_at = $2
               WHERE project_id = $3 AND scene_number = $4r,   r4   r]   r%   r0   r   s        r   update_image_statusrb           D||~ 
 
ll>LL&
 	
 	

 
 
 
	

 
 
 
f   BA=BA?B:B'B(B,B7B8B?BBBBBBBc                8  K   t                d {   }|j                         4 d {   }|j                  d|t        j                  t
        j                        | |       d {    d d d       d {    y 7 l7 U7 7 # 1 d {  7  sw Y   y xY ww)NzUPDATE images SET file_path = $1, status = 'generated', updated_at = $2
               WHERE project_id = $3 AND scene_number = $4r,   )r4   r]   rW   r0   r   s        r   update_image_pathrf     s     D||~ 
 
ll>LL&
 	
 	

 
 
 
	

 
 
 
rd   c           
     N  K   t                d{   }|j                         4 d{   }|j                  d||t        |      t	        j
                  t        j                        | |       d{    ddd      d{    y7 w7 `7 7 # 1 d{  7  sw Y   yxY ww)z(Store image binary data in the database.NzUPDATE images SET image_data = $1, mime_type = $2, 
               file_size_bytes = $3, updated_at = $4
               WHERE project_id = $5 AND scene_number = $6)r   r   r   r\   r   r-   r   r.   )r4   r]   rX   rY   r0   r   s         r   update_image_datarh     s      D||~ 
 
ll> 
OLL&

 
	
 
	

 
 
 

	

 
 
 
sg   B%BB%B
B%AB2B3B7B%BB%
B%BB%B"BB"B%c                  K   t                d {   }|j                         4 d {   }|j                  d|        d {   }g }|D ]}  }t        |      }t	        |d         |d<   |j                  d      r|d   j                         |d<   |j                  d      r|d   j                         |d<   |j                  |        |cd d d       d {    S 7 7 7 7 # 1 d {  7  sw Y   y xY ww)NzSELECT id, project_id, scene_number, image_prompt,
                      file_path, mime_type, file_size_bytes, status, 
                      created_at, updated_at
               FROM images WHERE project_id = $1
               ORDER BY scene_numberr4   r=   
updated_atrM   r4   r0   r   rP   r9   rQ   rR   s          r   get_project_imagesrl   $       D||~  ZZ(
 
 
  	AQA!!L/2AlOuu\""#L/";";"=,uu\""#L/";";"=,MM!	 %   
   g   C<CC<C!C<C'C#B	C'C<C%C<!C<#C'%C<'C9-C0.C95C<c                :  K   t                d{   }|j                         4 d{   }|j                  d| |       d{   }|r|d   |d   xs dfcddd      d{    S 	 ddd      d{    y7 k7 T7 ;7 7 # 1 d{  7  sw Y   yxY ww)z$Get image binary data and mime type.NzdSELECT image_data, mime_type FROM images 
               WHERE project_id = $1 AND scene_number = $2rX   rY   rT   )NrT   r   r   r>   r4   r]   r0   r   rE   s        r   get_image_datarr   ;        D||~ 	! 	!MM>	
 
 |$c+&6&E+E	! 	! 	! !	! 	! 	! 	!
	! 	! 	! 	! 	!   BA<BA>BBB BB$B%B*B+B6B7B>B BBBBBBBc                   K   t        | |       d{   \  }}|r%t        j                  |      j                         |fS d|fS 7 2w)zGet image as Base64 string.N)rr   base64	b64encodedecode)r4   r]   datarY   s       r   get_image_data_base64rz   L  sM      +:|DDOD)%,,.	99? Es   AA3Ac                   K   t                d{   }|j                         4 d{   }|j                  d| |       d{   }|dk(  cddd      d{    S 7 N7 77 7 # 1 d{  7  sw Y   yxY ww)z5Delete an image record for a given project and scene.Nz>DELETE FROM images WHERE project_id = $1 AND scene_number = $2rI   r3   )r4   r]   r0   r   r9   s        r   delete_imager|   V  s     D||~ $ $||P
 

 #$ $ $ $
$ $ $ $sf   A<AA<A!A<A'A#A'A<A%A<!A<#A'%A<'A9-A0.A95A<	video/mp4)video_promptrW   
video_datarY   duration_secondsr%   c                h  K   t                d {   }t        j                  t        j                        }	|rt        |      nd }
|j                         4 d {   }|j                  d| ||||||
|||	|	       d {    d d d       d {    y 7 7 ;7 7 # 1 d {  7  sw Y   y xY ww)Na  INSERT INTO videos (project_id, scene_number, video_prompt,
                                   file_path, video_data, mime_type, file_size_bytes,
                                   duration_seconds, status, created_at, updated_at)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
               ON CONFLICT(project_id, scene_number)
               DO UPDATE SET video_prompt = EXCLUDED.video_prompt,
                             file_path = EXCLUDED.file_path,
                             video_data = COALESCE(EXCLUDED.video_data, videos.video_data),
                             mime_type = EXCLUDED.mime_type,
                             file_size_bytes = COALESCE(EXCLUDED.file_size_bytes, videos.file_size_bytes),
                             duration_seconds = COALESCE(EXCLUDED.duration_seconds, videos.duration_seconds),
                             status = EXCLUDED.status,
                             updated_at = EXCLUDED.updated_atr[   )r4   r]   r~   rW   r   rY   r   r%   r0   r-   r^   r   s               r   upsert_videor   g  s      D
,,x||
$C#-J4I||~ 
 
llA 1
 	
 	

 
 
	 
	

 
 
 
sj   B2BA
B2BB2 B?B BB2BB2B2BB2B/#B&$B/+B2c                  K   t                d {   }|j                         4 d {   }|j                  d|        d {   }g }|D ]}  }t        |      }t	        |d         |d<   |j                  d      r|d   j                         |d<   |j                  d      r|d   j                         |d<   |j                  |        |cd d d       d {    S 7 7 7 7 # 1 d {  7  sw Y   y xY ww)Na  SELECT id, project_id, scene_number, video_prompt,
                      file_path, mime_type, file_size_bytes, duration_seconds,
                      status, created_at, updated_at
               FROM videos WHERE project_id = $1
               ORDER BY scene_numberr4   r=   rj   rM   rk   s          r   get_project_videosr     rm   rn   c                8  K   t                d {   }|j                         4 d {   }|j                  d|t        j                  t
        j                        | |       d {    d d d       d {    y 7 l7 U7 7 # 1 d {  7  sw Y   y xY ww)NziUPDATE videos SET status = $1, updated_at = $2
               WHERE project_id = $3 AND scene_number = $4r,   ra   s        r   update_video_statusr     rc   rd   c                :  K   t                d{   }|j                         4 d{   }|j                  d| |       d{   }|r|d   |d   xs dfcddd      d{    S 	 ddd      d{    y7 k7 T7 ;7 7 # 1 d{  7  sw Y   yxY ww)z$Get video binary data and mime type.NzdSELECT video_data, mime_type FROM videos 
               WHERE project_id = $1 AND scene_number = $2r   rY   r}   )Nr}   rp   rq   s        r   get_video_datar     rs   rt   
chatterboxz	audio/wav)tts_texttts_providerrW   
audio_datarY   r   r%   c        	        j  K   t                d {   }	t        j                  t        j                        }
|rt        |      nd }|	j                         4 d {   }|j                  d| ||||||||||
|
       d {    d d d       d {    y 7 7 <7 7 # 1 d {  7  sw Y   y xY ww)Na  INSERT INTO audios (project_id, scene_number, tts_text, tts_provider,
                                   file_path, audio_data, mime_type, file_size_bytes,
                                   duration_seconds, status, created_at, updated_at)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
               ON CONFLICT(project_id, scene_number)
               DO UPDATE SET tts_text = EXCLUDED.tts_text,
                             tts_provider = EXCLUDED.tts_provider,
                             file_path = EXCLUDED.file_path,
                             audio_data = COALESCE(EXCLUDED.audio_data, audios.audio_data),
                             mime_type = EXCLUDED.mime_type,
                             file_size_bytes = COALESCE(EXCLUDED.file_size_bytes, audios.file_size_bytes),
                             duration_seconds = COALESCE(EXCLUDED.duration_seconds, audios.duration_seconds),
                             status = EXCLUDED.status,
                             updated_at = EXCLUDED.updated_atr[   )r4   r]   r   r   rW   r   rY   r   r%   r0   r-   r^   r   s                r   upsert_audior     s      D
,,x||
$C#-J4I||~ 
 
llA 5
 	
 	

 
 
	 
	

 
 
 
sj   B3BA
B3BB3!B BBB3BB3B3BB3B0$B'%B0,B3c                  K   t                d {   }|j                         4 d {   }|j                  d|        d {   }g }|D ]}  }t        |      }t	        |d         |d<   |j                  d      r|d   j                         |d<   |j                  d      r|d   j                         |d<   |j                  |        |cd d d       d {    S 7 7 7 7 # 1 d {  7  sw Y   y xY ww)Na  SELECT id, project_id, scene_number, tts_text, tts_provider,
                      file_path, mime_type, file_size_bytes, duration_seconds,
                      status, created_at, updated_at
               FROM audios WHERE project_id = $1
               ORDER BY scene_numberr4   r=   rj   rM   rk   s          r   get_project_audiosr     rm   rn   c                   t        |       }t        |d         |d<   dD ]E  }|j                  |      }|g ||<   t        |t              s-	 t	        j
                  |      ||<   G |j                  d      r|d   j                         |d<   |j                  d      r|d   j                         |d<   |S # t        j                  t        f$ r g ||<   Y w xY w)z=Convert a character DB row to a dict with parsed JSON fields.r/   style_keywordsnegative_keywordshard_prohibitionsr=   rj   )	r?   rB   r@   rA   r7   rC   JSONDecodeError	TypeErrorrD   )rE   rR   keyvals       r   _char_row_to_dictr     s    S	A!D'lAdG L eeCj;AcFS!C# 	uu\L/335,uu\L/335,H (()4 #s   B77CCF)physical_descriptionr   r   r   camera_settingslighting_settings	pose_ruleenvironment_ruleclothing_ruleanatomical_highlight_rulesconsequence_philosophyshot_variety_rulesthumbnail_path
is_defaultc                  K   t                d {   }t        j                  t        j                        }|j                         4 d {   }|j                  d| ||t        j                  |xs g       t        j                  |xs g       t        j                  |xs g       ||||	|
|||||||       d {    d d d       d {    y 7 7 7 7 # 1 d {  7  sw Y   y xY ww)Na  INSERT INTO characters
               (id, name, physical_description, style_keywords, negative_keywords,
                hard_prohibitions, camera_settings, lighting_settings, pose_rule,
                environment_rule, clothing_rule, anatomical_highlight_rules,
                consequence_philosophy, shot_variety_rules,
                thumbnail_path, is_default, created_at, updated_at)
               VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
               ON CONFLICT (id) DO NOTHING)	r   r   r-   r   r.   r   r   r7   r8   )r/   namer   r   r   r   r   r   r   r   r   r   r   r   r   r   r0   r-   r   s                      r   insert_characterr   2  s     & D
,,x||
$C||~ 
 
ll.  JJ~+,JJ(.B/JJ(.B/&"5
 	
 	

 
 
 
	

 
 
 
sj   C/C;C/CC/A,C<C=CC/CC/C/CC/C, C#!C,(C/c           	       K   t                d{   }dD ]6  }||v st        ||   t              st        j                  ||         ||<   8 t        j                  t        j                        |d<   g }g }t        |j                         d      D ]/  \  }\  }}|j                  | d|        |j                  |       1 dj                  |      }|j                  |        |j                         4 d{   }	 |	j                  d| d	t        |       g|  d{   }
|
d
k(  cddd      d{    S 7 27 G7 7 # 1 d{  7  sw Y   yxY ww)z)Update one or more fields on a character.Nr   rj      )start = $, zUPDATE characters SET z WHERE id = $zUPDATE 1)r   rA   listr7   r8   r   r-   r   r.   	enumerateitemsrO   joinr   r   r\   )char_idupdatesr0   r   	set_partsvaluesivalue
set_clauser   r9   s              r   update_characterr   g  s[    D L 4'>jt<::gcl3GCL4 %LL6GL IF$W]]_A> <CC5QC)e 9%J
MM'||~ $ $#t||$ZLc&k]K
NT
 
 #	$ $ $' &$
$ $ $ $st   E EE E C
E =E>E &E'E(E0E <E	=E E E	E EEEE c                   K   t                d {   }|j                         4 d {   }|j                  d|        d {   }|dk(  cd d d       d {    S 7 M7 67 7 # 1 d {  7  sw Y   y xY ww)Nz$DELETE FROM characters WHERE id = $1rI   r3   )r   r0   r   r9   s       r   delete_characterr     su     D||~ $ $||$JGTT#$ $ $ $T$ $ $ $rK   c                  K   t                d {   }|j                         4 d {   }|j                  d|        d {   }|rt        |      nd cd d d       d {    S 7 W7 @7 (7 # 1 d {  7  sw Y   y xY ww)Nz&SELECT * FROM characters WHERE id = $1r   r   r>   r   )r   r0   r   rE   s       r   get_characterr     sz     D||~ 7 7MM"JGTT), %$7 7 7 7T7 7 7 7f   BA(BA*BA0A,A0B"A.#B*B,A0.B0B6A97B>Bc                  K   t                d {   }|j                         4 d {   }|j                  d|        d {   }|rt        |      nd cd d d       d {    S 7 W7 @7 (7 # 1 d {  7  sw Y   y xY ww)Nz(SELECT * FROM characters WHERE name = $1r   )r   r0   r   rE   s       r   get_character_by_namer     sz     D||~ 7 7MM"LdSS), %$7 7 7 7S7 7 7 7r   c                 *  K   t                d {   } | j                         4 d {   }|j                  d       d {   }|D cg c]  }t        |       c}cd d d       d {    S 7 `7 I7 2c c}w 7 # 1 d {  7  sw Y   y xY ww)Nz;SELECT * FROM characters ORDER BY is_default DESC, name ASC)r   r   rN   r   r0   r   rP   rQ   s       r   list_charactersr     s     D||~ 4 4ZZI
 
 /33!!$3	4 4 4 4
 4	4 4 4 4x   BA1BA3BA>A5A>A7A>B+A<,B3B5A>7A><B>BBBBc                    K   t                d {   } | j                         4 d {   }|j                  d       d {   }|r|d   ndcd d d       d {    S 7 P7 97 "7 # 1 d {  7  sw Y   y xY ww)Nz(SELECT COUNT(*) as count FROM characterscountr   rp   r0   r   rE   s      r   character_countr     sv     D||~ * *MM"LMM"s7|* * * *M* * * *sf   A>A!A>A#A>A)A%A)A>A'A>#A>%A)'A>)A;/A20A;7A>c                  K   t                d{   }|j                         4 d{   }|j                  d|        d{   }|r|d   ndcddd      d{    S 7 Q7 :7 "7 # 1 d{  7  sw Y   yxY ww)zGet a setting value by key.Nz)SELECT value FROM settings WHERE key = $1r   rp   )r   r0   r   rE   s       r   get_settingr     sx     D||~ - -MM"MsSS"s7|- - - -S- - - -sf   A?A"A?A$A?A*A&A*A?A(A?$A?&A*(A?*A<0A31A<8A?c                4  K   t                d{   }|j                         4 d{   }|r|j                  d| ||       d{    n|j                  d| |       d{    ddd      d{    y7 h7 Q7 57 7 # 1 d{  7  sw Y   yxY ww)zSet a setting value (upsert).Nz
                INSERT INTO settings (key, value, description)
                VALUES ($1, $2, $3)
                ON CONFLICT (key) DO UPDATE SET value = $2, description = $3
                z
                INSERT INTO settings (key, value)
                VALUES ($1, $2)
                ON CONFLICT (key) DO UPDATE SET value = $2
                r3   )r   r   descriptionr0   r   s        r   set_settingr     s     D||~  ,,
 	 	 	 ,,
      	   sx   BA9BA;BBA=B#A?$B(B3B4B;B=B?BBB	B
BBc                 &  K   t                d{   } | j                         4 d{   }|j                  d       d{   }|D ci c]  }|d   |d    c}cddd      d{    S 7 ^7 G7 0c c}w 7 # 1 d{  7  sw Y   yxY ww)zGet all settings as a dict.NzSELECT key, value FROM settingsr   r   )r   r   rN   )r0   r   rP   rE   s       r   get_all_settingsr     s     D||~ : :ZZ ABB489SE
CL(9: : : :B9: : : :sx   BA/BA1BA<A3A<A5A<B)A:*B1B3A<5A<:B<BBB
Bc                   K   t                d{   }|j                         4 d{   }|j                  d|        d{    ddd      d{    y7 H7 17 7 # 1 d{  7  sw Y   yxY ww)zDelete a setting by key.Nz#DELETE FROM settings WHERE key = $1r3   )r   r0   r   s      r   delete_settingr     sx     D||~ G Gll@#FFFG G G GFG G G Gsf   A6AA6AA6A!AA!A6AA6A6A!A6!A3'A*(A3/A6c                  K   t                d{   }|j                         4 d{   }|j                  d| |||       d{   }t        |      cddd      d{    S 7 V7 ?7 $7 # 1 d{  7  sw Y   yxY ww)z(Insert a new Grok account and return it.NzINSERT INTO grok_accounts (label, sso_token, sso_rw_token, user_id)
               VALUES ($1, $2, $3, $4)
               RETURNING *r   r   r>   r?   )label	sso_tokensso_rw_tokenuser_idr0   r   rE   s          r   insert_grok_accountr     s     D||~  MM 9lG	
 
 Cy   
   sf   BA'BA)BA/A+A/B!A-"B)B+A/-B/B5A86B=Bc                 *  K   t                d{   } | j                         4 d{   }|j                  d       d{   }|D cg c]  }t        |       c}cddd      d{    S 7 `7 I7 2c c}w 7 # 1 d{  7  sw Y   yxY ww)zList all Grok accounts.Nz/SELECT * FROM grok_accounts ORDER BY created_at)r   r   rN   r?   r   s       r   list_grok_accountsr     s     D||~ ' 'ZZ=
 
 "&&AQ&	' ' ' '
 '	' ' ' 'r   c                   K   t                d{   } | j                         4 d{   }|j                  d       d{   }|rt        |      ndcddd      d{    S 7 V7 ?7 (7 # 1 d{  7  sw Y   yxY ww)zPick the least-recently-used active Grok account (atomic rotation).

    Uses FOR UPDATE SKIP LOCKED to handle concurrent batch runs safely.
    Resets daily_usage_count when date has changed.
    Na  
            UPDATE grok_accounts
            SET last_used_at = NOW(),
                usage_count = usage_count + 1,
                daily_usage_count = CASE
                    WHEN daily_usage_reset_at IS NULL OR daily_usage_reset_at < CURRENT_DATE THEN 1
                    ELSE daily_usage_count + 1
                END,
                daily_usage_reset_at = CURRENT_DATE
            WHERE id = (
                SELECT id FROM grok_accounts
                WHERE is_active = TRUE
                ORDER BY last_used_at NULLS FIRST, usage_count ASC
                LIMIT 1
                FOR UPDATE SKIP LOCKED
            )
            RETURNING *
        r   r   s      r   get_next_grok_accountr     s      D||~ * *MM #  $  tCyT'* * * ** * * *sf   BA'BA)BA/A+A/B!A-"B)B+A/-B/B5A86B=Bc                  K   h d}|j                         D ci c]  \  }}||v s|| }}}|syt                d{   }dj                  d t        |      D              }| gt	        |j                               z   }|j                         4 d{   }	 |	j                  d| dg|  d{   }
|
rt        |
      ndcddd      d{    S c c}}w 7 7 K7 .7 # 1 d{  7  sw Y   yxY ww)zSUpdate a Grok account. Accepts: label, sso_token, sso_rw_token, user_id, is_active.>   r   r   	is_activer   r   Nr   c              3  6   K   | ]  \  }}| d |dz      yw)r   r   Nr   ).0r   ks      r   	<genexpr>z&update_grok_account.<locals>.<genexpr>  s"     K1qcacUOKs   zUPDATE grok_accounts SET z WHERE id = $1 RETURNING *)	r   r   r   r   r   r   r   r>   r?   )
account_idfieldsallowedr   vr   r0   set_clausesr   r   rE   s              r   update_grok_accountr     s     LG &?1!w,q!t?G?D))K	'8JKKK\D!122F||~ * *!DMM'}4NO

 
  tCyT* * * @ *
* * * *sz   C=CCC=C  AC=C"C=C(5C$6C(C=C&C="C=$C(&C=(C:.C1/C:6C=c                   K   t                d{   }|j                         4 d{   }|j                  d|        d{   }|dk(  cddd      d{    S 7 M7 67 7 # 1 d{  7  sw Y   yxY ww)z/Delete a Grok account. Returns True if deleted.Nz'DELETE FROM grok_accounts WHERE id = $1rI   r3   )r   r0   r   r9   s       r   delete_grok_accountr   )  s}     D||~ $ $||5z
 
 #	$ $ $ $
$ $ $ $rK   )returnNone)r   zasyncpg.Pool)r/   rB   r%   rB   r&   rB   r'   rB   r(   rB   r)   rB   r*   rB   r   r   )r4   rB   r%   rB   r   r   )r$   )r4   rB   r9   dict[str, Any]r*   rB   r   r   )r4   rB   r   dict[str, Any] | None)r4   rB   r   bool)r   list[dict[str, Any]])r4   rB   r]   intrV   rB   rW   rB   rX   bytes | NonerY   rB   r%   rB   r   r   )r4   rB   r]   r   r%   rB   r   r   )r4   rB   r]   r   rW   rB   r   r   )rT   )
r4   rB   r]   r   rX   bytesrY   rB   r   r   )r4   rB   r   r   )r4   rB   r]   r   r   ztuple[bytes | None, str])r4   rB   r]   r   r   ztuple[str | None, str])r4   rB   r]   r   r   r   )r4   rB   r]   r   r~   rB   rW   rB   r   r   rY   rB   r   float | Noner%   rB   r   r   )r4   rB   r]   r   r   rB   r   rB   rW   rB   r   r   rY   rB   r   r   r%   rB   r   r   )rE   r   r   r   )"r/   rB   r   rB   r   rB   r   list[str] | Noner   r   r   r   r   rB   r   rB   r   rB   r   rB   r   rB   r   rB   r   rB   r   rB   r   rB   r   r   r   r   )r   rB   r   r   r   r   )r   rB   r   r   )r   rB   r   r   )r   rB   r   r   )r   r   )r   rB   r   
str | None)N)r   rB   r   rB   r   r   r   r   )r   zdict[str, str])r   rB   r   r   )
r   rB   r   rB   r   rB   r   rB   r   r   )r   r   )r   r   r   r   )r   r   r   r   )<__doc__
__future__r   r7   osr   r   pathlibr   typingr   r   rv   r   r	   __annotations__environr@   r
   EnvironmentErrorr   r   r    r1   r5   r:   rG   rJ   rS   r_   rb   rf   rh   rl   rr   rz   r|   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>r      s=   #  	 '      $ $ zz~~n-
	D $N  

 
 	

 
 
 
 
 

6
 ;=+47	(0$6 # &
&
 &
 	&

 &
 &
 &
 &
 
&
R



 MX

#&
49
FI
	
&.!!#&!!"#&	$* # %))
)
 )
 	)

 )
 )
 )
 #)
 )
 
)
X.

!!#&!!4 $# %),
,
 ,
 	,

 ,
 ,
 ,
 ,
 #,
 ,
 
,
^8< !#'+*.*.&("$ #2
2
 2
 	2

 %2
 (2
 (2
 2
 2
 2
 2
 2
 !$2
  2
 2
  !2
" #2
$ 
%2
j$8$774*-6:G
'*:*"$r   