
    >9i\4                     8   d Z ddlmZ ddlmZmZ ddlmZ ddlmZm	Z	m
Z
 ddlmZ ddlmZ ddlmZmZ dd	lmZ dd
lmZmZmZmZ ddlmZ ddlmZ ddlmZ ddl m!Z! ddl"m#Z#m$Z$m%Z%m&Z& de'de'fdZ( edg      Z)d9dede*dz  de'fdZ+dede*e'   fdZ,d Z-dede*e'   de*e'   fdZ.e)j_                  de*e$   d       	 	 d:ded!ed"ed#e0d$edz  f
d%       Z1e)je                  de$e
jf                  d&'      dede#d!ed"efd(       Z4d)ed"edefd*Z5e)jm                  d+e$d,       d)ede%d!ed"efd-       Z7e)jq                  d+d.d/0      d)ed!ed"eddfd1       Z9e)je                  d2e$d3       d)ede&d!ed"efd4       Z:e)jq                  d5e$d6       d)ed7e;d!ed"efd8       Z<y);u  
訊息 API(階段 3a)。

  - GET    /api/channels/{cid}/messages           ─ 列訊息(預設最新 50 則)
  - POST   /api/channels/{cid}/messages           ─ 發訊息
  - PATCH  /api/messages/{id}                     ─ 編輯訊息(只有作者)
  - DELETE /api/messages/{id}                     ─ 刪訊息(作者或 owner)
  - POST   /api/messages/{id}/reactions           ─ 加表情回應
  - DELETE /api/messages/{id}/reactions/{emoji}   ─ 收回自己的表情回應

階段 3a 簡化:不檢查使用者是否「在頻道裡」(public 頻道任何人都能讀寫)。
階段 3 後續會補上「private 頻道才需要 ChannelMember 才能讀寫」。
    )defaultdict)datetimetimezoneUUID)	APIRouterHTTPExceptionstatus)select)selectinload)CurrentUser	DBSession)manager)ChannelChannelMemberMessageMessageReaction)User)trigger_coba_if_mentioned)trigger_index)trigger_tagging)MessageCreateRequestMessagePublicMessageUpdateRequestReactionRequestpayloadreturnc                 v    ddl }ddlm ddlm fd}|j	                  |j                  | |            S )uJ   把 datetime / UUID 轉成可序列化的字串(WebSocket 用 JSON 走)。r   N)r   r   c                     t        |       r| j                         S t        |       rt        |       S t        dt	        |              )Nzunserialisable )
isinstance	isoformatstr	TypeErrortype)o_UUIDr   s    4/volume1/homes/robertsu/coba/app/app/api/messages.pydefaultz_ws_safe.<locals>.default,   sB    a";;= aq6M/$q'344    )r(   )jsonr   uuidr   loadsdumps)r   r*   r(   r&   r   s      @@r'   _ws_safer.   &   s/    !"5 ::djj'j:;;r)   messages)tagsNmsgread_byc                    t        t              }| j                  D ]*  }||j                     j	                  |j
                         , |j                         D cg c]  \  }}|t        |      |d }}}| j                  | j                  | j                  rB| j                  j                  | j                  j                  | j                  j                  dnd| j                  | j                  | j                  | j                   | j"                  | j$                  | j&                  ||xs g | j(                  xs g dS c c}}w )u   把 SQLAlchemy Message 轉成 MessagePublic 格式(含 reactions 彙整 + 已讀名單)。

    Args:
        msg: 訊息物件
        read_by: 已讀此訊息的使用者清單(不含作者),由 caller 計算後傳入
    )emojicountuser_idsiddisplay_name
avatar_urlN)r8   
channel_idusercontentmessage_typereply_to_id	is_pinned	is_edited
created_at
updated_at	reactionsr2   r0   )r   listrD   r4   appenduser_iditemslenr8   r;   r<   r9   r:   r=   r>   r?   r@   rA   rB   rC   r0   )r1   r2   groupedrr4   uidsrD   s          r'   _serialize_messagerM   9   s    $G]]		*  #==?*KE4 #d)>*  
 ffnn
 XX ((++HH11((--
  ;;((]]]]nnnn=bB# 	s   Er;   c           	      2  K   t        t        t              j                  t        t        j                  t        j
                  k(        j                  t        j                  | k(  t        j                  j                  d      t        j                  j                  d            }|j                  |       d{   j                         }|D cg c]4  \  }}|j
                  |j                  |j                  |j                  d6 c}}S 7 Tc c}}w w)u   取得頻道內所有成員的「最後讀取時間」+ 個人簡介。

    回傳格式:[{user_id, display_name, avatar_url, last_read_at}, ...]
    給 caller 用來計算每則訊息的 read_by。
    N)rG   r9   r:   last_read_at)r   r   r   joinrG   r8   wherer;   rO   is_not
deleted_atis_executeallr9   r:   )r;   dbstmtrowsmemberr<   s         r'   _get_channel_read_statesr[   \   s      	}d#	dM))TWW4	5	$$
2&&--d3OO%

 	 **T""'')D ! !LFD ww --//"//		
 !  #s$   B8D:D;D9DDDc                 d    | y| j                    | j                  t        j                        S | S )ue  把 datetime 統一成 timezone-aware UTC,避免 naive vs aware 比較炸鍋。

    背景:SQLite 用 ADD COLUMN 加進去的 TIMESTAMP 欄位讀出來是 naive(沒 tz),
    但 server_default=func.now() 建的欄位讀出來是 aware。直接比較會 TypeError。
    這個 helper 把兩種都當 UTC 處理(SQLite 內部本來就是 UTC 存的)。
    N)tzinfo)r]   replacer   utc)dts    r'   _normalize_dtra   w   s0     
z	yyzzz..Ir)   member_readsc                     t        | j                        }|D cg c]4  }t        |d         |k\  r!|d   | j                  k7  r|d   |d   |d   d6 c}S c c}w )u^   根據訊息建立時間 + 成員 last_read_at,算出哪些人已讀此訊息(不含作者)。rO   rG   r9   r:   r7   )ra   rB   rG   )r1   rb   msg_createdms       r'   _build_read_byrf      st    /K  A>*+{:q|s{{?Z I,n-L/	

   s   9Az#/api/channels/{channel_id}/messagesu   列出頻道訊息)response_modelsummarycurrent_userrW   limitbeforec           
      ^  K   |j                  t        t              j                  t        j                  | k(  t        j
                  j                  d                   d{   j                         }|t        dd      t        t              j                  t        j                  | k(  t        j
                  j                  d            j                  t        j                  j                               j                  t        |d            }||j                  t        t              j                  t        j                  |k(               d{   j                         }|,|j                  t        j                  |j                  k        }t!        |j                  |       d{   j#                         j%                               }|j'                          t)        | |       d{   }	|D 
cg c]  }
t+        |
t-        |
|	             c}
S 7 7 7 q7 /c c}
w w)uv   列訊息,預設最新 50 則。

    傳 ?before=<message_id> 可以分頁載入更舊的(infinite scroll up)。
    N     找不到此頻道status_codedetail   )rU   r   r   rQ   r8   rS   rT   scalar_one_or_noner	   r   r;   order_byrB   descrj   minrE   scalarsrV   reverser[   rM   rf   )r;   ri   rW   rj   rk   chrX   
cursor_msgr/   rb   re   s              r'   list_messagesr{      s    " 

wgjjJ68J8J8N8Nt8TU   
z4HII 	w	w!!Z/1C1C1G1G1M	N	'$$))+	,	s5#		 	 JJ7O!!'**"67
 
 	 !::g00:3H3HHID2::d++446::<=H 2*bAALLTUHqq.L"ABHUU5
 ,
 BUs]   A H-"H#C5H-H"AH-2H$3AH-6H&7H-?H(H-"H-$H-&H-(H-u	   發訊息)rg   rp   rh   c                   K   |j                  t        t              j                  t        j                  | k(  t        j
                  j                  d                    d {   j                         }|t        dd      t        | |j                  |j                  |j                  |j                        }|j                  |       |j                  t        t              j                  t        j                  | k(  t        j                   |j                  k(               d {   j                         }|(t        | |j                        }|j                  |       t#        j$                  t&        j(                        |_        |j-                          d {    |j                  t        t              j/                  t1        t        j2                              j                  t        j                  |j                  k(               d {   j5                         }t7        | |       d {   }t9        |t;        ||            }	t=        j>                  tA        dtC        |       |	d             d {    tE        | |j                  |j                  |j                         tG        |j                         tI        |j                  |j                  |j                   |j                  |jJ                  r|jJ                  jM                         nd	|jN                  
       |	S 7 7 7 7 &7 7 w)Nrm   rn   ro   )r;   rG   r=   r>   r?   )r;   rG   message_newr$   r;   message)rG    )
message_idr;   rG   r=   created_at_isor0   )(rU   r   r   rQ   r8   rS   rT   rs   r	   r   r=   r>   r?   addr   r;   rG   r   nowr   r_   rO   commitoptionsr   rD   
scalar_oner[   rM   rf   
ws_manager	broadcastr.   r"   r   r   r   rB   r!   r0   )
r;   r   ri   rW   ry   r1   	my_memberfullrb   
serializeds
             r'   create_messager      s     

wgjjJ68J8J8N8Nt8TU   
z4HII
))''C FF3K zz}##$$
2!!\__4	
 
   !ZQ	
y%\\(,,7I
))+**w	g//0	1	wzzSVV#	$  z|	 	
 2*bAAL#D.|*LMJ 

x*o)      j#&&'//<??[CFF66>>58^^s~~//1XX u  
 Bsq   A M"L5#CM*L8+A7M"L;#A5ML>!M:M;AMMB2M8M;M>MMMr   c                 d  K   t        t              j                  t        t        j                              j                  t        j                  | k(  t        j                  j                  d             }|j                  |       d {   j                         }|t        dd      |S 7 #w)Nrm   u   找不到此訊息ro   )r   r   r   r   rD   rQ   r8   rS   rT   rU   rs   r	   )r   rW   rX   r1   s       r'   _get_message_or_404r     s     w	g//0	1	wzzZ'););)?)?)E	F 	
 D!!
5
5
7C
{4HIIJ "s   BB0
B.$B0z/api/messages/{message_id}u   編輯訊息c                 
  K   t        | |       d {   }|j                  |j                  k7  rt        dd      |j                  |_        d|_        |j                          d {    |j                  |       d {    t        |j                  |       d {   }t        |t        ||            }t        j                  t        dt        |j                        |d             d {    |S 7 7 7 {7 _7 w)N  u$   只有作者能編輯自己的訊息ro   Tmessage_updatedr~   )r   rG   r8   r	   r=   rA   r   refreshr[   r;   rM   rf   r   r   r.   r"   )r   r   ri   rW   r1   rb   r   s          r'   update_messager     s      $J3
3C
{{loo%4Z[[//CKCM
))+
**S/1#.."EEL#C\)JKJ


x!#..))     
  4
 EsY   DC9AD(C;)DC=DC? AD2D3D;D=D?DD   u   刪除訊息)rp   rh   c           	        K   t        | |       d {   }|j                  |j                  k7  r|j                  dk7  rt	        dd      t        j                  t        j                        |_	        |j                          d {    t        j                  t        dt        |j                        t        |j                        d             d {    y 7 7 W7 	w)Nownerr   u%   只有作者或 owner 能刪除訊息ro   message_deleted)r$   r;   r   )r   rG   r8   roler	   r   r   r   r_   rS   r   r   r   r.   r"   r;   )r   ri   rW   r1   s       r'   delete_messager   /  s      $J3
3C
{{loo%,*;*;w*F4[\\\\(,,/CN
))+


x!#..)#&&k)      4
 s5   C#CA4C#CAC#C!C#C#!C#z$/api/messages/{message_id}/reactionsu   對訊息加表情回應c                   K   t        | |       d {   }|j                  t        t              j	                  t        j
                  | k(  t        j                  |j                  k(  t        j                  |j                  k(               d {   j                         }|]|j                  t        | |j                  |j                               |j                          d {    t        | |       d {   }t        |j                  |       d {   }t        |t        ||            }t!        j"                  t%        dt'        |j                        |d             d {    |S 7 c7 7 7 ~7 b7 w)N)r   rG   r4   reaction_changedr~   )r   rU   r   r   rQ   r   rG   r8   r4   rs   r   r   r[   r;   rM   rf   r   r   r.   r"   )r   r   ri   rW   r1   existingrb   r   s           r'   add_reactionr   C  sN     $J3
3C jj%%&&*4##|6!!W]]2	
    
! OO--
 	
 iik'
B771#.."EEL#C\)JKJ


x"#..))     
 7 4 	7Esl   FE5A=FE8AF)E:*F=E<>FE>AF.F /F8F:F<F>F Fz,/api/messages/{message_id}/reactions/{emoji}u   收回自己的表情回應r4   c                   K   |j                  t        t              j                  t        j                  | k(  t        j
                  |j                  k(  t        j                  |k(               d {   j                         }|r1|j                  |       d {    |j                          d {    t        | |       d {   }t        |j                  |       d {   }t        |t        ||            }t!        j"                  t%        dt'        |j                        |d             d {    |S 7 7 7 7 }7 a7 w)Nr   r~   )rU   r   r   rQ   r   rG   r8   r4   rs   deleter   r   r[   r;   rM   rf   r   r   r.   r"   )r   r4   ri   rW   r   r1   rb   r   s           r'   remove_reactionr   l  s      jj%%&&*4##|6!!U*	
    ii!!!iik#J3
3C1#.."EEL#C\)JKJ


x"#..))     
 ' 	"
3Esm   A0E2D?3(EEE3E4EEE%E&AE8E	9EEEEE	E)N)2   N)=__doc__collectionsr   r   r   r+   r   fastapir   r	   r
   
sqlalchemyr   sqlalchemy.ormr   app.core.depsr   r   app.core.ws_managerr   r   app.models.channelr   r   r   r   app.models.userr   app.services.coba_chatr   app.services.coba_memoryr   app.services.coba_taggerr   app.schemas.channelr   r   r   r   dictr.   routerrE   rM   r[   ra   rf   getintr{   postHTTP_201_CREATEDr   r   patchr   r   r   r   r"   r    r)   r'   <module>r      s   $ '  4 4  ' 0 5 O O   < 2 4 <d <t <  
	% G  dTk  T  Ft DJ 6 tDz d4j  )&    &V&V&V 	&V 	&V
 4K&V
&VR ) ''	  AA!A A 		AAH	$ 	I 	' 	 *=R`a!  		 b. +nU$/5>	 V& * &  
!!! ! 		!
!H 2 )  
  		
r)   