
    Ij                     x   % S r SSKrSSKrSSKrSSKrSSKJrJr  SSKJ	r	J
r
Jr  SSKJrJr  SSKJr  \R"                  S   r\R"                  S   r\R)                  S	S
5      S-   r\" S5      r\R.                  " S\R0                  5      rSrSr\" \5      r\\\4   \S'   \	" SSSS9r S\
4S jr!S\4S jr"\ RG                  S5      S\
4S j5       r$\ RK                  S5      S\
4S j5       r&\ RG                  S5      S 5       r'\ RK                  S5      S\
4S j5       r(\ RK                  S5      S\
4S  j5       r)g)!u  
HomeBase Public API — read-only public proxy to Turso.

Architecture: FastAPI app behind Cloudflare Tunnel (no Cloudflare Access).
- GET /             → serves homebase.html (the shared view)
- POST /turso       → validates SELECT-only SQL, forwards to Turso HTTP API
- GET /healthz      → liveness check
- Everything else   → 404

Security layers (defense in depth):
  1. Read-only Turso token (DB-level enforcement)
  2. SELECT-only SQL allowlist (proxy-level enforcement)
  3. Per-IP rate limiting (10 req/sec, burst 30)
  4. noindex header (prevents Google indexing)

This service is PUBLIC by design — no auth wall. Both the token scope
and the SQL allowlist prevent writes; the rate limit deters scraping
and bill-spiking.
    N)defaultdictdeque)FastAPIRequestHTTPException)FileResponseJSONResponse)PathTURSO_DATABASE_URLTURSO_AUTH_TOKENz	libsql://zhttps://z/v2/pipelinez'/home/ubuntu/second-brain/homebase.htmlz^\s*SELECT\bg      ?   _ip_hitszHomeBase Public)titledocs_url	redoc_urlrequestc                    U R                   R                  S5      =(       d    U R                  R                  n[        R                  " 5       n[
        U   nU(       a9  US   U[        -
  :  a)  UR                  5         U(       a  US   U[        -
  :  a  M)  [        U5      [        :  a
  [        SSS9eUR                  U5        g)z@Sliding-window rate limit. Rejects with 429 if IP exceeds burst.zCF-Connecting-IPr   i  zRate limit exceededstatus_codedetailN)headersgetclienthosttimer   RATE_LIMIT_WINDOWpopleftlenRATE_LIMIT_BURSTr   append)r   ipnowhitss       0/home/ubuntu/second-brain/homebase-public/app.pycheck_rate_limitr%   /   s    			/	0	GGNN4G4GB
))+CB<D
47S#444 47S#444
4y$$4IJJKK    bodyc                     U R                  S/ 5      nU Hf  nUR                  S5      S:w  a  M  UR                  S0 5      R                  SS5      n[        R                  U5      (       a  MX  [        SSUS	S
  3S9e   g	)zCWalk the Turso pipeline payload; reject anything that isn't SELECT.requeststypeexecutestmtsql i  z%Only SELECT statements allowed. Got: NP   r   )r   ALLOWED_SQL_PATTERNmatchr   )r'   r)   reqr-   s       r$   validate_sql_statementsr3   <   s}    xx
B'H776?i'ggfb!%%eR0"((-->s3BxjI  r&   /c                 B   #    [        U 5        [        [        SSS.S9$ 7f)Nznoindex, nofollowzstrict-origin-when-cross-origin)zX-Robots-TagzReferrer-Policy)r   )r%   r   HOMEBASE_HTML)r   s    r$   serve_homebaser7   J   s(     W/@
 s   z/tursoc                   #    [        U 5        U R                  5       I S h  vN n[        U5        [        R                  " SS9 IS h  vN nUR                  [        USS[         30S9I S h  vN nS S S 5      IS h  vN   [        WR                  5       UR                  S9$  N N^ N: N,! , IS h  vN  (       d  f       NA= f7f)Nr   timeoutAuthorizationBearer jsonr   contentr   )
r%   r>   r3   httpxAsyncClientpostTURSO_HTTP_URLr   r	   r   )r   r'   r   resps       r$   turso_proxyrF   V   s     WDD!  ,,[[$0@/A&BC ! 
 
 -, 		9I9IJJ   -
 -,,,si   CB&'C	B(
C"B./B*0B.4C?B, 'C(C*B.,C.C4B75CCz/healthzc                     #    SS0$ 7f)Nstatusok rJ   r&   r$   healthzrK   e   s     ds   z/votec                 ,  #    [        U 5        U R                  5       I S h  vN nUR                  S5      nUR                  S5      nU(       a  US;  a
  [        SSS9eUS:X  a  SOS	n[        R
                  " S
S9 IS h  vN nUR                  [        SSSU SU S3SUS./S.S./0SS[         30S9I S h  vN nS S S 5      IS h  vN   [        WR                  5       UR                  S9$  N Nr N: N,! , IS h  vN  (       d  f       NA= f7f)N
listing_id	direction)updown  zInvalid voter   rO   votes_up
votes_downr   r9   r)   r+   zUPDATE apartment_listings SET z = COALESCE(z, 0) + 1 WHERE id = ?text)r*   value)r-   args)r*   r,   r;   r<   r=   r?   )r%   r>   r   r   rA   rB   rC   rD   r   r	   r   )r   r'   rM   rN   colr   rE   s          r$   voterX   j   s4    WD,'J%I.8NCC!T)*|C  ,,[[	Ecdgchhtuxty  zO  DP  dj  u  [@  ZA  <B   C  D  E$0@/A&BC ! 
 
 -, 		9I9IJJ   -
 -,,,sj   DC2ADC4D6C:;C6<C: DC8'D4D6C:8D:D DDDz/geocodec                   #    [        U 5        U R                  5       I S h  vN nUR                  SS5      nU(       d
  [        SSS9e[        R
                  " SS9 IS h  vN nUR                  SUS	S
S.SS0S9I S h  vN nS S S 5      IS h  vN   WR                  5       nU(       d  [        S S S.5      $ [        [        US   S   5      [        US   S   5      S.5      $  N N Ni N[! , IS h  vN  (       d  f       Np= f7f)Naddressr.   rQ   zAddress requiredr   
   r9   z*https://nominatim.openstreetmap.org/searchr>      )qformatlimitz
User-AgentzHomeBase-SF/1.0)paramsr   )latlngr   ra   lon)r%   r>   r   r   rA   rB   r	   float)r   r'   rZ   r   rE   resultss         r$   geocoderf   |   s     WDhhy"%G4FGG  ,,ZZ8 FQ?!#45   
 
 -, iikGD677gaj&7 8wqzRWGXAYZ[[   -
 -,,,sj   DC&?D!C("D%C. C*C.DC,AD(D*C.,D.D4C75DD)*__doc__osrer   rA   collectionsr   r   fastapir   r   r   fastapi.responsesr   r	   pathlibr
   environr   r   replacerD   r6   compile
IGNORECASEr0   r   r   r   dictstr__annotations__appr%   r3   r   r7   rC   rF   rK   rX   rf   rJ   r&   r$   <module>rv      s  & 
 	   * 3 3 8  ZZ 45 ::01 #++KD~U>? jj"--@    (/$sEz
 /%E
g 
$  '   (Kw K K   'K K K" *\7 \ \r&   