ó
     ïÒjQF  ã                   óš  • S r SSKrSSKrSSKrSSKJrJr  SSKrSSK	r
Sr\R                  " S5      r/ SQ/ SQ/ SQS	.r\" \R#                  \S
   \S   -   \S   -   5      5      rSrS rSr " S S\5      rS-S jrS rS rS rS rS rS rS rS r S r!S r"S r#S r$S r%S  r&S! r'S" r(S# r)S$ r*S% r+S& r,S.S' jr-S( r.S) r/S* r0S+ r1S, r2g)/zúAccounts and tenancy: admin (platform) -> parent (tenant) -> kid (sub-tenant).

Every parent owns only the kids whose parent_id is theirs; a parent may own at most max_kids kids.
A suspended parent blocks its own login and the logins of all its kids.é    N)Úgenerate_password_hashÚcheck_password_hash)ÚadminÚparentÚkidz^[a-z0-9][a-z0-9._-]{2,29}$)u   ðŸ�±u   ðŸ�¶u   ðŸ¦Šu   ðŸ�¼u   ðŸ�¨u   ðŸ�¯õ   ðŸ¦�u   ðŸ�¸u   ðŸ�µu   ðŸ¦„u   ðŸ�°u   ðŸ�»u   ðŸ�§u   ðŸ¦‰u   ðŸ�™u   ðŸ¦–u   ðŸ�¬u   ðŸ¦‹u   ðŸ�¢õ   ðŸš€õ   âš½u   ðŸŒŸ)u   ðŸ§‘â€�ðŸš€u   ðŸ¦¸u   ðŸ¥·u   ðŸ§™õ   ðŸ�ºõ   ðŸ¦…u   ðŸ�‰u   ðŸ¦ˆõ   ðŸŽ§õ   ðŸŽ®õ   ðŸ�€r
   õ   ðŸŽ¸õ   ðŸ›¹õ   ðŸ“·u   ðŸ§­õ   âš¡õ   ðŸ”¥õ   ðŸŒ�r	   õ   ðŸ�†õ   ðŸŽ¯)r   r   u   ðŸ§ u   ðŸ“šu   ðŸ–‹ï¸�u   ðŸ’»r   r   r
   u   ðŸ�Žï¸�r   r   u   ðŸŒŒr   r   r   u   ðŸ�”ï¸�r   r   r   r   r   )ÚkidsÚjuniorÚteenr   r   r   é   c                 óÈ   • [         R                  " U 5      (       a&  [         R                  " U 5      R                  SS5      OSn[        R                  U[        S   5      S   $ )zcFirst avatar of the age band of the kid's course level (a cat for 8 years old, an explorer for 11).Úbandr   r   )ÚCURÚlevelÚgetÚAVATARS_BY_BAND)r   r   s     Úaccounts.pyÚdefault_avatarr#      sJ   € ä36·9²9¸U×3CÑ3CŒ3�9Š9�UÓ×Ñ ¨Ô/È€DÜ×Ñ˜t¤_°VÑ%<Ó=¸aÑ@Ð@ó    aë  
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT UNIQUE NOT NULL,
    role TEXT NOT NULL CHECK(role IN ('admin','parent','kid')),
    password_hash TEXT NOT NULL,
    display_name TEXT NOT NULL,
    parent_id INTEGER,
    max_kids INTEGER DEFAULT 3,
    active INTEGER DEFAULT 1,
    avatar TEXT DEFAULT '',
    unlock_all INTEGER DEFAULT 0,
    course_level INTEGER DEFAULT 1,
    created_at TEXT DEFAULT (datetime('now','localtime')),
    last_login TEXT
)c                   ó   • \ rS rSrSrSrg)ÚAccountErroré/   z:A user-facing problem (shown in Arabic in the dashboards).© N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__static_attributes__r(   r$   r"   r&   r&   /   s   † ÜDr$   r&   c           
      ó   • [         R                  " 5       nUR                  S5      R                  5       =(       d    SS0S   =(       d    SnSU;  Ga=  U (       Ga5  S GH  nUR                  SU45      R                  5       nU R	                  UR                  5        S35      =(       d    SR                  5       R                  5       U R	                  UR                  5        S35      peU(       d  M–  U(       d  MŸ  U(       d  M¨  UR                  S	XTS
   45      R                  5       (       a  MÓ  UR                  SU[        U5      U R	                  UR                  5        S35      =(       d    UUS
   45        GM!     UR                  5         SU;  Gah  UR                  S5        UR                  S5        UR                  S5      R                  5        Vs/ s H  owS   PM	     nnUR                  S5        UR                  [        5        S V	s/ s H  o™U;   d  M
  U	PM     n
n	UR                  SSR                  U
5       SSR                  U
5       S35        UR                  S5        UR                  S5      R                  5       nU(       a  UR                  SUS
   45        UR                  S[        R                  45        UR                  5         UR                  S5        UR                  S5        SUR                  S5      R                  5        Vs/ s H  owS   PM	     sn;  a  UR                  S5        UR                  S[        S    45        UR                  5         UR!                  5         g!s  snf s  sn	f s  snf )"a  Upgrade the old users table (kid/parent only) to the tenancy schema, keeping ids and data.
The old app copied PARENT_*/KID_* from .env into its single parent and kid at every start; that copy is done one
last time here, so the family keeps the login it had in .env and its progress.z0SELECT sql FROM sqlite_master WHERE name='users'ÚsqlÚ z'admin')r   r   z5SELECT id FROM users WHERE role=? ORDER BY id LIMIT 1Ú	_USERNAMEÚ	_PASSWORDz.SELECT 1 FROM users WHERE username=? AND id<>?ÚidzGUPDATE users SET username=?, password_hash=?, display_name=? WHERE id=?Ú_DISPLAY_NAMEzPRAGMA foreign_keys=OFFzPRAGMA legacy_alter_table=ONzPRAGMA table_info(users)Únamez%ALTER TABLE users RENAME TO users_old)r4   ÚusernameÚroleÚpassword_hashÚdisplay_nameÚ
created_atzINSERT INTO users (Ú,z	) SELECT z FROM users_oldzDROP TABLE users_oldz<SELECT id FROM users WHERE role='parent' ORDER BY id LIMIT 1z-UPDATE users SET parent_id=? WHERE role='kid'z0UPDATE users SET course_level=? WHERE role='kid'zPRAGMA legacy_alter_table=OFFzPRAGMA foreign_keys=ONÚcourse_levelz;ALTER TABLE users ADD COLUMN course_level INTEGER DEFAULT 1zLUPDATE users SET avatar=? WHERE role='kid' AND (avatar IS NULL OR avatar='')r   N)ÚdbÚget_dbÚexecuteÚfetchoner    ÚupperÚstripÚlowerÚ_legacyÚcommitÚfetchallÚSCHEMAÚjoinr   ÚDEFAULTÚAVATARSÚclose)ÚenvÚcr0   r8   ÚrowÚunÚpwÚrÚold_colsÚxÚkeepÚpars               r"   ÚmigraterW   3   sç  € ô 	�	Š	‹€AØ�9‰9ÐGÓH×QÑQÓS×bÐX]Ð_aÐWbÐdiÑ
j×
pÐnp€CØ˜Ô§Ü%ˆDØ—)‘)ÐSÐVZÐU\Ó]×fÑfÓhˆCØ—g‘g §¡£ ¨iÐ8Ó9×?¸R×FÑFÓH×NÑNÓPÐRU×RYÑRYÐ]a×]gÑ]gÓ]iÐ\jÐjsÐZtÓRu�ßˆs—r�rŸb˜b¨¯©Ð3cÐfhÐnrÑjsÐetÓ)u×)~Ñ)~÷  *Aó  *AØ—	‘	ÐcØœw r›{¨C¯G©G°t·z±z³|°nÀMÐ4RÓ,S×,YÐWYÐ[^Ð_cÑ[dÐe÷gñ	 &ð 	
�‰Œ
Ø˜ÔØ	�	‰	Ð+Ô,Ø	�	‰	Ð0Ô1Ø'(§y¡yÐ1KÓ'L×'UÑ'UÔ'WÓXÒ'W !�f”IÑ'WˆÐXØ	�	‰	Ð9Ô:Ø	�	‰	”&ÔÙdÓvÒd�aÐmuÑhu—ÑdˆÐvØ	�	‰	Ð'¨¯©°«Ð'7°yÀÇÁÈ$ÃÐ@PÐP_Ð`ÔaØ	�	‰	Ð(Ô)à�i‰iÐVÓW×`Ñ`ÓbˆÞØ�I‰IÐEÈÈDÉ	À|ÔTØ	�	‰	ÐDÄsÇ{Á{ÀnÔUØ	�‰Œ
Ø	�	‰	Ð1Ô2Ø	�	‰	Ð*Ô+Ø°·±Ð;UÓ1V×1_Ñ1_Ô1aÓbÒ1a¨A œiÑ1aÑbÓbØ	�	‰	ÐOÔPØ‡I�IÐ\Ô_fÐghÑ_iÐ^kÔlØ‡H�H„JØ‡G�G…Iùò% Yùò wùò cs   ÇNÈ	NÈ!NÌ!Nc                 ó   • [        U SS9$ )Nzpbkdf2:sha256:600000)Úmethod)r   ©rQ   s    r"   Úhash_pwr[   Z   s   € Ü! "Ð-CÑDÐDr$   c                 óh   • [         R                  " U R                  S5      5      R                  5       $ )zFThe Science app's first version stored a plain sha256 of the password.zutf-8)ÚhashlibÚsha256ÚencodeÚ	hexdigestrZ   s    r"   rE   rE   ^   s#   € ä�>Š>˜"Ÿ)™) GÓ,Ó-×7Ñ7Ó9Ð9r$   c                 ó&  • U S   n[         R                  " SU5      (       af  U[        U5      :X  aV  [        R                  " 5       nUR                  S[        U5      U S   45        UR                  5         UR                  5         gg[        X!5      $ )Nr9   z[0-9a-f]{64}ú+UPDATE users SET password_hash=? WHERE id=?r4   TF)
ÚreÚ	fullmatchrE   r>   r?   r@   r[   rF   rL   r   )ÚuserrQ   ÚhrN   s       r"   Úcheck_pwrg   c   su   € Øˆ_Ñ€AÜ	‡|‚|�O Q×'Ñ'Ø”˜“ÓÜ—	’	“ˆAØ�I‰IÐCÄgÈbÃkÐSWÐX\ÑS]ÐE^Ô_Ø�H‰HŒJ˜Ÿ™œ	ØØÜ˜qÓ%Ð%r$   c                  ó   • / SQn [        S5       Vs/ s H9  n[        R                  " U 5      R                  SS5      R                  SS5      PM;     nnUS    SUS    S[        R                  " S	5      S
-    3$ s  snf )uI   Easy for a child to type: 2 short words + 2 digits, e.g. Â«sun-kite-47Â».)ÚsunÚcatÚbookÚstarÚkiteÚfrogÚmoonÚcakeÚballÚtreeÚfishÚbusÚduckÚlampÚshipÚrainé   ô   Ã©Úeô   Ã¨r   Ú-é   éZ   é
   )ÚrangeÚsecretsÚchoiceÚreplaceÚ	randbelow)ÚwordsÚ_Úws      r"   Úgen_passwordr‰   o   s|   € ò J€EÜNSÐTUÌhÓWÊhÈŒ�Š˜Ó	×	&Ñ	& t¨SÓ	1×	9Ñ	9¸$ÀÖ	DÉh€AÐWØ�‰dˆV�1�Q�q‘T�F˜!œG×-Ò-¨bÓ1°BÑ6Ð7Ð8Ð8ùò 	Xs   ’A A;c                 óz   • U (       a  [        U 5      S:  a  [        S5      e[        U 5      S:”  a  [        S5      eg )Né   uG   ÙƒÙ„Ù…Ø© Ø§Ù„Ù…Ø±ÙˆØ± ÙŠØ¬Ø¨ Ø£Ù† ØªÙƒÙˆÙ† 6 Ø£Ø­Ø±Ù� Ø¹Ù„Ù‰ Ø§Ù„Ø£Ù‚Ù„éd   u)   ÙƒÙ„Ù…Ø© Ø§Ù„Ù…Ø±ÙˆØ± Ø·ÙˆÙŠÙ„Ø© Ø¬Ø¯Ø§Ù‹)Úlenr&   rZ   s    r"   Úcheck_new_passwordrŽ   v   s7   € Þ”�R“˜1“ÜÐdÓeÐeÜ
ˆ2ƒw�ƒ}ÜÐFÓGÐGð r$   c                 ó¶   • [         R                  " 5       nUR                  SU 45      R                  5       nUR	                  5         U(       a  [        U5      $ S $ )NzSELECT * FROM users WHERE id=?)r>   r?   r@   rA   rL   Údict)ÚuidrN   rR   s      r"   r    r    ~   sC   € Ü
�	Š	‹€AØ	�	‰	Ð2°S°FÓ;×DÑDÓF€AØ‡G�G„IÞŒ4�‹7Ð!˜TÐ!r$   c                 ó   • [         R                  " 5       nUR                  SU =(       d    SR                  5       R	                  5       45      R                  5       nUR                  5         U(       a  [        U5      $ S $ )Nz$SELECT * FROM users WHERE username=?r1   )r>   r?   r@   rC   rD   rA   rL   r�   )r7   rN   rR   s      r"   Úby_usernamer“   …   sZ   € Ü
�	Š	‹€AØ	�	‰	Ð8¸H¿NÈ×;QÑ;QÓ;S×;YÑ;YÓ;[Ð:]Ó^×gÑgÓi€AØ‡G�G„IÞŒ4�‹7Ð!˜TÐ!r$   c                 óP   • S Vs0 s H  oU R                  U5      _M     sn$ s  snf )zDWhat the dashboards may see of an account (never the password hash).)r4   r7   r8   r:   Ú	parent_idÚmax_kidsÚactiveÚavatarÚ
unlock_allr=   r;   Ú
last_login)r    )ÚuÚks     r"   Úpublicr�   Œ   s;   € ñ "só  tò  "s˜Aˆq�u‰u�Q‹xŠKñ  "sñ  tð  tùò  ts   …#c                 ó˜   • U (       a
  U S   (       d  gU S   S:X  a/  U S   (       a%  [        U S   5      n[        U=(       a    US   5      $ g)z/Active account, and for a kid an active parent.r—   Fr8   r   r•   T)r    Úbool)r›   Úps     r"   Ú	can_loginr¡   ‘   sE   € æ�A�h—KØØˆ�y�EÓ˜a ŸnÜ��+‘ÓˆÜ�A×%˜!˜H™+Ó&Ð&Ør$   c                 ó  • [        U 5      nU(       a  [        X!=(       d    S5      (       d  g[        U5      (       d  g[        R                  " 5       nUR                  SUS   45        UR                  5         UR                  5         US 4$ )Nr1   )NuD   Ø§Ø³Ù… Ø§Ù„Ù…Ø³ØªØ®Ø¯Ù… Ø£Ùˆ ÙƒÙ„Ù…Ø© Ø§Ù„Ù…Ø±ÙˆØ± ØºÙŠØ± ØµØ­ÙŠØ­Ø©)Nu?   Ù‡Ø°Ø§ Ø§Ù„Ø­Ø³Ø§Ø¨ Ù…ÙˆÙ‚ÙˆÙ�. ØªÙˆØ§ØµÙ„ Ù…Ø¹ Ø§Ù„Ù…Ø³Ø¤ÙˆÙ„.zBUPDATE users SET last_login=datetime('now','localtime') WHERE id=?r4   )r“   rg   r¡   r>   r?   r@   rF   rL   )r7   Úpasswordr›   rN   s       r"   Úauthenticater¤   ›   sk   € Ü�HÓ€AÞ”H˜Q§¨B×/Ñ/Ø[Ü�Q�<‰<ØVÜ
�	Š	‹€AØ‡I�IÐRÐUVÐW[ÑU\ÐT^Ô_Ø‡H�H„JØ‡G�G„IØˆdˆ7€Nr$   c           	      ón  • [         R                  " 5       nUR                  SU  S3U5      R                  5       nUR	                  5         / nU Hc  n[        [        U5      5      nUR                  [        US   US   5      [        US   S-  5      US   US   US	   S
9  UR                  U5        Me     U$ )zUKids with their totals in one query: xp, finished activities, last activity, minutes.a`  
        SELECT u.*, COALESCE(a.xp,0) AS xp, COALESCE(a.secs,0) AS secs, a.last_at AS last_activity,
               COALESCE(a.done,0) AS done, COALESCE(p.stars,0) AS stars, COALESCE(p.pts,0) AS pts
        FROM users u
        LEFT JOIN (SELECT user_id, SUM(points_earned) xp, SUM(time_spent) secs, MAX(attempted_at) last_at, COUNT(DISTINCT CASE WHEN partial=0 THEN activity_id END) done
                   FROM activity_attempts GROUP BY user_id) a ON a.user_id=u.id
        LEFT JOIN (SELECT user_id, total_points pts, total_stars stars FROM user_stats) p ON p.user_id=u.id
        WHERE u.role='kid' AND z' ORDER BY u.display_name COLLATE NOCASEÚxpÚptsÚsecsé<   Úlast_activityÚdoneÚstars)r¦   Úminutesrª   r«   r¬   )r>   r?   r@   rG   rL   r�   r�   ÚupdateÚmaxÚroundÚappend)ÚwhereÚargsrN   ÚrowsÚoutrR   Úds          r"   Úkid_stats_sqlr·   ¨   s×   € ä
�	Š	‹€AØ�9‰9ð  ð !&˜wÐ&MðQð SWóX÷ YaÑX`ÓXbð 	ð ‡G�G„IØ
€CÛˆÜ”4˜“7‹OˆØ	�‰”C˜˜$™  5¡Ó*´E¸!¸F¹)Àb¹.Ó4IÐYZÐ[jÑYkÐrsÐtzÑr{ð  DEð  FMñ  DNˆñ  	OØ�
‰
�1Žñ ð €Jr$   c                 ó   • [        SU 45      $ )Nzu.parent_id=?©r·   ©r•   s    r"   Úkids_ofr»   ¼   s   € Ü˜¨9¨,Ó7Ð7r$   c                  ó   • [        SS5      $ )Nz1=1r(   r¹   r(   r$   r"   Úall_kidsr½   À   s   € Ü˜ Ó#Ð#r$   c            
      óô   • [         R                  " 5       n U R                  S5      R                  5       nU R	                  5         U Vs/ s H"  n[        [        [        U5      5      US   S9PM$     sn$ s  snf )Nz¼SELECT u.*, (SELECT COUNT(*) FROM users k WHERE k.parent_id=u.id AND k.role='kid') AS kids
                        FROM users u WHERE u.role='parent' ORDER BY u.display_name COLLATE NOCASEr   )r   )r>   r?   r@   rG   rL   r�   r�   )rN   r´   rR   s      r"   Úparentsr¿   Ä   sb   € Ü
�	Š	‹€AØ�9‰9ð eó fßfnÑfnÓfpð 	à‡G�G„IÙ;?Ó@º4°aŒD”œ˜Q›“ q¨¡yÔ1¹4Ñ@Ð@ùÒ@s   Á	)A5c                 ó˜   • [         R                  " 5       nUR                  SU 45      R                  5       S   nUR	                  5         U$ )Nz=SELECT COUNT(*) n FROM users WHERE role='kid' AND parent_id=?Ún)r>   r?   r@   rA   rL   )r•   rN   rÁ   s      r"   Ú
count_kidsrÂ   Ì   s?   € Ü
�	Š	‹€AØ	�	‰	ÐQÐT]ÐS_Ó`×iÑiÓkÐloÑp€AØ‡G�G„IØ€Hr$   c                  ón  ^• [         R                  " 5       mSU4S jjn [        U " S5      U " S5      U " S5      U " S5      U " S5      U " S5      [        U " S5      S	-  5      U " S
5      TR	                  S5      R                  5        Vs0 s H  oS   US   _M     snS9	nTR                  5         U$ s  snf )Nc                 óH   >• TR                  X5      R                  5       S   $ )Nr   )r@   rA   )ÚsÚarN   s     €r"   Ú<lambda>Úsummary.<locals>.<lambda>Õ   s   ø€ ˜Ÿ	™	 !›×0Ñ0Ó2°1Ò5r$   z.SELECT COUNT(*) FROM users WHERE role='parent'z+SELECT COUNT(*) FROM users WHERE role='kid'z;SELECT COUNT(*) FROM users WHERE role='parent' AND active=1zfSELECT COUNT(DISTINCT user_id) FROM activity_attempts WHERE date(attempted_at)=date('now','localtime')zoSELECT COUNT(DISTINCT user_id) FROM activity_attempts WHERE attempted_at>=datetime('now','localtime','-7 days')z`SELECT COUNT(*) FROM activity_attempts WHERE attempted_at>=datetime('now','localtime','-7 days')zsSELECT COALESCE(SUM(time_spent),0) FROM activity_attempts WHERE attempted_at>=datetime('now','localtime','-7 days')r©   z?SELECT COALESCE(SUM(max_kids),0) FROM users WHERE role='parent'zOSELECT course_level, COUNT(*) FROM users WHERE role='kid' GROUP BY course_levelr   r~   )	r¿   r   Úactive_parentsÚ
kids_todayÚ	kids_weekÚattempts_weekÚminutes_weekÚseatsÚ	per_level)r(   )r>   r?   r�   r°   r@   rG   rL   )ÚqrR   rµ   rN   s      @r"   ÚsummaryrÑ   Ó   sü   ø€ Ü
�	Š	‹€AÞ5€AÜ
‘qÐIÓJÑQRð  TAó  RBÙÐ ]Ó^Ùð  Eó  FÙð  Mó  NÙð   Bó  CÜ!¡!ð  %Zó  #[ð  ^`ñ  #`ó  aÙÐXÓYØ./¯i©ið  9Jó  /K÷  /Tñ  /Tô  /Vó  Wò  /V¨˜A™$  !¡š*ñ  /Vñ  WñX€Cð ‡G�G„IØ€Jùò Ws   Â	B2c                 óÜ   • U =(       d    SR                  5       R                  5       n[        R                  U5      (       d  [	        S5      e[        U5      (       a  [	        SU S35      eU$ )Nr1   uŸ   Ø§Ø³Ù… Ø§Ù„Ù…Ø³ØªØ®Ø¯Ù…: 3 Ø¥Ù„Ù‰ 30 Ø­Ø±Ù�Ø§Ù‹ Ø¥Ù†Ø¬Ù„ÙŠØ²ÙŠØ§Ù‹ ØµØºÙŠØ±Ø§Ù‹ Ø£Ùˆ Ø£Ø±Ù‚Ø§Ù…Ø§Ù‹ (ÙˆÙŠÙ�Ø³Ù…Ø­ Ø¨Ù€ . _ -)ØŒ ÙˆÙŠØ¨Ø¯Ø£ Ø¨Ø­Ø±Ù� Ø£Ùˆ Ø±Ù‚Ù…u   Ø§Ø³Ù… Ø§Ù„Ù…Ø³ØªØ®Ø¯Ù… Â«u8   Â» Ù…Ø³ØªØ®Ø¯Ù… Ù…Ù† Ù‚Ø¨Ù„ØŒ Ø§Ø®ØªØ± Ø§Ø³Ù…Ø§Ù‹ Ø¢Ø®Ø±)rC   rD   ÚUSER_REÚmatchr&   r“   )r7   r›   s     r"   Ú_clean_usernamerÕ   ã   se   € Ø	��R×ÑÓ ×&Ñ&Ó(€AÜ�=‰=˜×ÑÜð  }ó  ~ð  	~Ü�1‡~�~ÜÐ7¸°sÐ:rÐsÓtÐtØ€Hr$   c                 óz   • U =(       d    SR                  5       nU(       a  [        U5      S:”  a  [        S5      eU$ )Nr1   é(   uH   Ø§Ù„Ø§Ø³Ù… Ø§Ù„Ù…Ø¹Ø±ÙˆØ¶ Ù…Ø·Ù„ÙˆØ¨ (40 Ø­Ø±Ù�Ø§Ù‹ Ø¹Ù„Ù‰ Ø§Ù„Ø£ÙƒØ«Ø±))rC   r�   r&   )r6   rÁ   s     r"   Ú_clean_namerØ   ì   s2   € Ø	��×ÑÓ€AÞ”�A“˜“ÜÐeÓfÐfØ€Hr$   c                 ó�   •  [        U 5      n SU s=::  a  S::  d  O  [        S5      eU $ ! [        [        4 a    [        S5      ef = f)NuD   Ø¹Ø¯Ø¯ Ø§Ù„Ø£Ø·Ù�Ø§Ù„ Ø§Ù„Ù…Ø³Ù…ÙˆØ­ ÙŠØ¬Ø¨ Ø£Ù† ÙŠÙƒÙˆÙ† Ø±Ù‚Ù…Ø§Ù‹r   é2   u2   Ø¹Ø¯Ø¯ Ø§Ù„Ø£Ø·Ù�Ø§Ù„ Ø§Ù„Ù…Ø³Ù…ÙˆØ­ Ø¨ÙŠÙ† 0 Ùˆ50)ÚintÚ	TypeErrorÚ
ValueErrorr&   )Úvs    r"   Ú
_clean_maxrß   ó   sS   € ðcÜ�‹Fˆð ��<�R�<ÜÐOÓPÐPØ€Høô	 ”zÐ"ó cÜÐaÓbÐbðcús	   ‚) ©Ac                 óî   •  [        U 5      n U [        R
                  ;  a5  [        R                  " U 5      n[        U(       a  SUS    S35      eS5      eU $ ! [        [        4 a    [        S5      ef = f)Nu&   Ø§Ø®ØªØ± Ø§Ù„Ù…Ø³ØªÙˆÙ‰ Ø§Ù„Ø¯Ø±Ø§Ø³ÙŠô   Â«Útitle_aru   Â» ØºÙŠØ± Ù…ØªØ§Ø­ Ø¨Ø¹Ø¯u'   Ù…Ø³ØªÙˆÙ‰ Ø¯Ø±Ø§Ø³ÙŠ ØºÙŠØ± Ù…Ø¹Ø±ÙˆÙ�)rÛ   rÜ   rÝ   r&   r   Ú	AVAILABLEr   )rÞ   Úlvs     r"   Ú_clean_levelrå   ý   sz   € ðEÜ�‹Fˆð 	”—‘ÓÜ�YŠY�q‹\ˆÜÎR˜R  :¡Ð/Ð/HÐIÓÐÐU~ÓÐØ€Høô ”zÐ"ó EÜÐCÓDÐDðEús   ‚A ÁA4c                 óÂ  • U [         ;   d   e[        U5      [        U5      p˜[        U5        U S:X  a]  [	        U5      n
U
(       a	  U
S   S:w  a  [        S5      e[        U5      U
S   :¼  a  [        SU
S    S35      eU(       a  U[        ;  a  SnU S:X  a  [        Ub  UO[        5      OS	nU S:X  a  [        Ub  UO[        [        S
S5      5      OSn[        R                  " 5       nUR                  SX€[!        U5      X�S:X  a  UOS X¶=(       d    U S:X  a  [#        U5      OSU45      nUR%                  5         UR&                  nUR)                  5         [	        U5      $ )Nr   r8   r   u#   ÙˆÙ„ÙŠ Ø§Ù„Ø£Ù…Ø± ØºÙŠØ± Ù…ÙˆØ¬ÙˆØ¯r–   u9   ÙˆØµÙ„ ÙˆÙ„ÙŠ Ø§Ù„Ø£Ù…Ø± Ø¥Ù„Ù‰ Ø§Ù„Ø­Ø¯ Ø§Ù„Ù…Ø³Ù…ÙˆØ­ (u6    Ø£Ø·Ù�Ø§Ù„). ÙŠÙ…ÙƒÙ† Ù„Ù„Ù…Ø³Ø¤ÙˆÙ„ Ø±Ù�Ø¹ Ø§Ù„Ø­Ø¯.r1   r   rJ   r~   zƒINSERT INTO users (username, role, password_hash, display_name, parent_id, max_kids, avatar, course_level) VALUES (?,?,?,?,?,?,?,?))ÚROLESrÕ   rØ   rŽ   r    r&   rÂ   rK   rß   ÚDEFAULT_MAX_KIDSrå   Úgetattrr   r>   r?   r@   r[   r#   rF   Ú	lastrowidrL   )r8   r7   r:   r£   r•   r–   r˜   r=   r›   rÁ   rV   ÚmkÚlvlrN   Úcurr‘   s                   r"   Úcreaterî     su  € Ø”5‹=Ðˆ=Ü˜8Ó$¤k°,Ó&?€qÜ�xÔ Øˆuƒ}Ü�)‹nˆÞ�c˜&‘k XÓ-ÜÐDÓEÐEÜ�iÓ  C¨
¡OÓ3ÜÐ!ZÐ[^Ð_iÑ[jÐZkð  lbð   có  dð  dÞ�f¤GÓ+ØˆFØOSÐW_ÓO_Œ Ñ 4‘HÔ:JÔ	KÐef€BØdhÐlqÓdqŒ, |Ñ'?‘|ÄWÌSÐR[Ð]^ÓE_Ô
`Ðwx€CÜ
�	Š	‹€AØ
�)‰)ð  ZØœg hÓ/°ÈÃ±IÐTXÐZ\÷  _Wð  AEð  INó  ANÔiwÐx{Ôi|ð  TVð  Y\ð  ]ó^€Cà‡H�H„JØ
�-‰-€CØ‡G�G„IÜˆs‹8€Or$   c                 ó  • [        U 5      nU(       d  [        S5      e0 nSU;   a  [        US   5      US'   SU;   a  US   (       a  SOSUS'   SU;   a)  US   S:X  a   US   [        ;  a  [        S	5      eUS   US'   S
U;   a  US   S:X  a  [	        US
   5      US
'   SU;   a  US   S:X  a  US   (       a  SOSUS'   SU;   a:  US   S:X  a1  [        US   5      n[        U 5      nXE:  a  [        SU S35      eXCS'   U(       at  [        R                  " 5       nUR                  SSR                  S U 5       5       S3/ UR                  5       QU P75        UR                  5         UR                  5         [        U 5      $ )Nu   Ø§Ù„Ø­Ø³Ø§Ø¨ ØºÙŠØ± Ù…ÙˆØ¬ÙˆØ¯r:   r—   r~   r   r˜   r8   r   u'   ØµÙˆØ±Ø© Ø±Ù…Ø²ÙŠØ© ØºÙŠØ± Ù…Ø¹Ø±ÙˆÙ�Ø©r=   r™   r–   r   u   Ù„Ø¯Ù‰ ÙˆÙ„ÙŠ Ø§Ù„Ø£Ù…Ø± uK    Ø£Ø·Ù�Ø§Ù„ Ø­Ø§Ù„ÙŠØ§Ù‹ØŒ Ù„Ø§ ÙŠÙ…ÙƒÙ† Ø¬Ø¹Ù„ Ø§Ù„Ø­Ø¯ Ø£Ù‚Ù„ Ù…Ù† Ø°Ù„ÙƒzUPDATE users SET z, c              3   ó*   #   • U  H	  oS -   v •  M     g7f)z=?Nr(   )Ú.0rœ   s     r"   Ú	<genexpr>Úupdate.<locals>.<genexpr>8  s   é € Ð/GÂ$¸Q°D¶Â$ùs   ‚z WHERE id=?)r    r&   rØ   rK   rå   rß   rÂ   r>   r?   r@   rI   ÚvaluesrF   rL   )r‘   Úfr›   Úsetsrë   ÚhaverN   s          r"   r®   r®     s–  € ÜˆC‹€AÞÜÐ;Ó<Ð<Ø€DØ˜ÓÜ*¨1¨^Ñ+<Ó=ˆˆ^ÑØ�1ƒ}Ø Ÿk™¨qˆˆX‰Ø�1ƒ}˜˜6™ eÓ+ØˆX‰;œgÓ%ÜÐHÓIÐIØ˜8™ˆˆX‰Ø˜Ó˜q ™y¨EÓ1Ü+¨A¨nÑ,=Ó>ˆˆ^ÑØ�qÓ˜Q˜v™Y¨%Ó/Ø"# L§/™Q°qˆˆ\ÑØ�Qƒ˜1˜V™9¨Ó0Ü˜˜*™Ó&ˆÜ˜#‹ˆØ‹9ÜÐ!:¸4¸&ð  ALð   Mó  Nð  NØˆZÑÞÜ�IŠI‹KˆØ	�	‰	Ð% d§i¡iÑ/GÁ$Ó/GÓ&GÐ%HÈÐTÐVkÐX\×XcÑXcÓXeÐVkÐgjÑVkÔlØ	�‰Œ
Ø	�‰Œ	Üˆs‹8€Or$   c                 ó¾   • [        U5        [        R                  " 5       nUR                  S[	        U5      U 45        UR                  5         UR                  5         g )Nrb   )rŽ   r>   r?   r@   r[   rF   rL   )r‘   rQ   rN   s      r"   Úset_passwordrù   >  s?   € Ü�rÔÜ
�	Š	‹€AØ‡I�IÐ;¼gÀb»kÈ3Ð=OÔPØ‡H�H„JØ‡G�G…Ir$   c                 ó  • [        U 5      nU(       a	  US   S:X  a  [        S5      eU /nUS   S:X  a[  [        R                  " 5       nX#R	                  SU 45      R                  5        Vs/ s H  oDS   PM	     sn-  nUR                  5         [        R                  " 5       nU H6  nS H  nUR	                  SU S	3U45        M     UR	                  S
U45        M8     UR                  5         UR                  5         [        U5      $ s  snf )z_Delete an account with everything it owns (a parent takes its kids and their progress with it).r8   r   u(   Ù„Ø§ ÙŠÙ…ÙƒÙ† Ø­Ø°Ù� Ù‡Ø°Ø§ Ø§Ù„Ø­Ø³Ø§Ø¨r   z&SELECT id FROM users WHERE parent_id=?r4   )Úactivity_attemptsÚexperiment_completionsÚsection_progressÚuser_badgesÚ
user_statszDELETE FROM z WHERE user_id=?zDELETE FROM users WHERE id=?)	r    r&   r>   r?   r@   rG   rL   rF   r�   )r‘   r›   ÚidsrN   rR   ÚiÚts          r"   Údeleter  F  sî   € äˆC‹€AÞ��&‘	˜WÓ$ÜÐEÓFÐFØˆ%€CØˆ�y�HÓÜ�IŠI‹KˆØ§¡Ð+SÐVYÐU[Ó!\×!eÑ!eÔ!gÓhÒ!g˜A�$”Ñ!gÑhÑhˆØ	�‰Œ	Ü
�	Š	‹€AÛˆÛqˆAØ�I‰I˜ Q CÐ'7Ð8¸1¸$Ö?ñ rà	�	‰	Ð0°1°$Ö7ñ ð ‡H�H„JØ‡G�G„IÜˆs‹8€Oùò is   Á+D
c                 ó„   • U (       a  U(       a	  US   S:w  a  gU S   S:H  =(       d    U S   S:H  =(       a    US   U S   :H  $ )z/Admin manages every kid; a parent only its own.r8   r   Fr   r   r•   r4   r(   )Úactorr   s     r"   Úcan_manage_kidr  [  sH   € æž˜s 6™{¨eÓ3ØØ�‰=˜GÑ#×f¨¨f©¸Ñ(A×(eÀcÈ+ÑFVÐZ_Ð`dÑZeÑFeÐfr$   c                 óŠ  • U R                  S5      =(       d    U R                  S5      =(       d    SR                  5       nU R                  S5      =(       d    U R                  S5      n[        U5      nU(       d]  U=(       d    [        R                  " S5      n[        SXR                  S5      =(       d    SU5        U(       d  [        S	U S
U S3SS9  O+U R                  S5      S:X  a  U(       a  [        US   U5        U R                  S5      =(       d    SR                  5       U R                  S5      =(       d    SR                  5       pe U(       aN  [        U5      (       d>  U R                  S5      (       a(  [        SXPR                  S5      =(       d    UU S   5        U(       a  [        U5      OSnU(       a[  U(       aS  [        U5      (       dB  U R                  S5      (       a+  [        SX`R                  S5      =(       d    UU S   US   S9  ggggg! [         a  n[        SU S3SS9   SnAgSnAff = f)zôCreate the admin and the first family from the environment when they do not exist yet.
Passwords set here are only initial ones: later changes from the dashboards are never overwritten,
except the admin's when ADMIN_RESET_PASSWORD=1 (recovery).ÚADMIN_USERNAMEÚAUTH_USERNAMEr   ÚADMIN_PASSWORDÚAUTH_PASSWORDé   ÚADMIN_DISPLAY_NAMEÚAdminz$*** admin account created: username=z
 password=z4 (set ADMIN_PASSWORD in .env to choose your own) ***T)ÚflushÚADMIN_RESET_PASSWORDÚ1r4   ÚPARENT_USERNAMEr1   ÚKID_USERNAMEÚPARENT_PASSWORDr   ÚPARENT_DISPLAY_NAMENÚKID_PASSWORDr   ÚKID_DISPLAY_NAMErº   z**** initial family from .env not created: z ***)	r    rD   r“   r‚   Útoken_urlsaferî   Úprintrù   r&   )	rM   Úadmin_uÚadmin_pwrÆ   rQ   ÚpuÚkur    r{   s	            r"   Ú	bootstrapr  c  sÔ  € ð
 �w‰wÐ'Ó(×O¨C¯G©G°OÓ,D×OÈ×VÑVÓX€GØ�w‰wÐ'Ó(×D¨C¯G©G°OÓ,D€HÜ�GÓ€AÞØ×2œ×.Ò.¨rÓ2ˆÜˆw˜§¡Ð)=Ó!>×!IÀ'È2ÔNÞÜÐ8¸¸	ÀÈBÈ4ð  PDð  Eð  MQò  RøØ	�‰Ð'Ó	(¨CÓ	/¶HÜ�Q�t‘W˜hÔ'Ø�g‰gÐ'Ó(×.¨B×5Ñ5Ó7¸#¿'¹'À.Ó:Q×:WÐUW×9^Ñ9^Ó9`ˆðPÞ”k "—o‘o¨#¯'©'Ð2C×*DÑ*DÜ�8˜R§¡Ð)>Ó!?×!EÀ2ÀsÐK\ÑG]Ô^Þ!ŒK˜ŒO tˆÞ–!œK¨ŸO™O°·±¸×0GÑ0GÜ�5˜"Ÿg™gÐ&8Ó9×?¸RÀÀ^ÑATÐ`aÐbfÑ`gÓhð 1H˜O�!ˆ2øäó PÜÐ:¸1¸#¸TÐBÈ$×OûðPús   ÅCH  È 
IÈ*H=È=I)N)NNr1   N)3r-   rc   r‚   r]   Úwerkzeug.securityr   r   Údb_initr>   Úlevelsr   rç   ÚcompilerÓ   r!   Úlistr�   ÚfromkeysrK   rè   r#   rH   Ú	Exceptionr&   rW   r[   rE   rg   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+  ðñKó 
Û Û ß IÛ Û à"€Ø
�*Š*Ð3Ó
4€ò |ò Dò Dñ€ñ
 ˆt�}‰}˜_¨VÑ4°ÀxÑ7PÑPÐSbÐciÑSjÑjÓkÓ
l€ØÐ òAð

€ô$E�9ô Eô#òNEò:ò
	&ò9òHò"ò"òtò
ò
òò(8ò$òAòòò òòòôò.ò>òò*góPr$   