a
    i;h                     @  s   d dl mZ d dlZd dlZd dlZd dlZd dlZd dlZd dlmZm	Z	m
Z
 d dlmZ ee jjZed Zed ZdZdd	d
dZdddddZdd	ddZdd	ddZdddddZdd	ddZddddd ZdVdddd!d"d#Zdd$dd%d&Zddd'd(d)Zd*d+d,ddddd$dd-d.d/ZdWddd0d1d2Zdddd3d4d5Z dXd7dd8d9d:Z!dddddd;d<d=Z"dYdd7dd?d@dAZ#dZddd0dBdCZ$dDdEdFdd7d7ddGdHdIZ%dd	dJdKZ&d[ddddLdMZ'ddddNdOZ(ddddPdQdRZ)ddSddTdUZ*dS )\    )annotationsN)datetime	timedeltatimezone)Pathzapp_config.jsonzlicense_server.dbz	0.1.0-devdict)returnc                   C  s>   t  si S ztt jddW S  tjy8   i  Y S 0 d S )Nutf-8encoding)APP_CONFIG_FILEexistsjsonloads	read_textJSONDecodeError r   r   8/home/ec2-user/npost_license/services/license_manager.pyload_app_config   s    r   None)configr   c                 C  s   t jtj| ddddd d S )NF   )ensure_asciiindentr	   r
   )r   
write_textr   dumps)r   r   r   r   save_app_config   s    r   zsqlite3.Connectionc                  C  s   t t} t j| _| S N)sqlite3connectLICENSE_DB_FILEZRowZrow_factory
connectionr   r   r   get_db_connection#   s    
r#   c                  C  sZ   t  @} | d | d | d | d |   W d    n1 sL0    Y  d S )Na"  
            CREATE TABLE IF NOT EXISTS admin_users (
                email TEXT PRIMARY KEY,
                status TEXT NOT NULL,
                plan_name TEXT NOT NULL,
                expires_at TEXT NOT NULL,
                device_fingerprint TEXT NOT NULL DEFAULT '',
                device_bound_at TEXT NOT NULL DEFAULT '',
                last_seen_at TEXT NOT NULL DEFAULT '',
                notes TEXT NOT NULL DEFAULT '',
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
            a  
            CREATE TABLE IF NOT EXISTS admin_messages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                message_type TEXT NOT NULL,
                target_email TEXT NOT NULL DEFAULT '',
                title TEXT NOT NULL,
                body TEXT NOT NULL,
                is_active INTEGER NOT NULL DEFAULT 1,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                UNIQUE(message_type, target_email)
            )
            a#  
            CREATE TABLE IF NOT EXISTS notice_posts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                body TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
            aM  
            CREATE TABLE IF NOT EXISTS inquiry_messages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                email TEXT NOT NULL,
                sender_type TEXT NOT NULL,
                title TEXT NOT NULL,
                body TEXT NOT NULL,
                created_at TEXT NOT NULL
            )
            )r#   executecommitr!   r   r   r   init_license_db)   s    r&   zsqlite3.Rowrowr   c                 C  sB   | d | d | d | d | d | d | d | d | d	 | d
 d
S )Nemailstatus	plan_name
expires_atZdevice_fingerprintZdevice_bound_atZlast_seen_atnotes
created_at
updated_at
r)   r*   planName	expiresAtdeviceFingerprintdeviceBoundAt
lastSeenAtr-   	createdAt	updatedAtr   r(   r   r   r   serialize_admin_user_rowd   s    r9   z
list[dict]c                  C  sB   t  } | d }W d    n1 s*0    Y  dd |D S )Nz
            SELECT email, status, plan_name, expires_at, device_fingerprint,
                   device_bound_at, last_seen_at, notes, created_at, updated_at
            FROM admin_users
            ORDER BY updated_at DESC
            c                 S  s   g | ]}t |qS r   )r9   .0r(   r   r   r   
<listcomp>}       z$load_admin_users.<locals>.<listcomp>)r#   r$   fetchall)r"   rowsr   r   r   load_admin_userss   s
    &r@   strdict | None)r)   r   c                 C  sd   t | pd  }|sd S t "}|d|f }W d    n1 sJ0    Y  |r`t|S d S )N z
            SELECT email, status, plan_name, expires_at, device_fingerprint,
                   device_bound_at, last_seen_at, notes, created_at, updated_at
            FROM admin_users
            WHERE email = ?
            )rA   striplowerr#   r$   fetchoner9   )r)   
normalizedr"   r(   r   r   r   find_admin_user   s    &	rH   )r)   updatesr   c                 C  s  t | pd  }|s tdttj }t	|}|t |pBi 
dpLdt |pVi 
dp`dt |pji 
dpttjtdd	  t |pi 
d
pdt |pi 
dpdt |pi 
dpdt |pi 
dpdt |pi 
dp||d
}|r|dd | D  t `}|d|d |d |d |d |d
 |d |d |d |d |d f
 |  W d    n1 s0    Y  t	|p|S )NrC      이메일이 필요합니다.r*   activer1      월구독형r2      daysr3   r4   r5   r-   r6   r0   c                 S  s   i | ]\}}|d ur||qS r   r   )r;   keyvaluer   r   r   
<dictcomp>   r=   z%upsert_admin_user.<locals>.<dictcomp>a  
            INSERT INTO admin_users (
                email, status, plan_name, expires_at, device_fingerprint,
                device_bound_at, last_seen_at, notes, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(email) DO UPDATE SET
                status=excluded.status,
                plan_name=excluded.plan_name,
                expires_at=excluded.expires_at,
                device_fingerprint=excluded.device_fingerprint,
                device_bound_at=excluded.device_bound_at,
                last_seen_at=excluded.last_seen_at,
                notes=excluded.notes,
                updated_at=excluded.updated_at
            r)   r7   )rA   rD   rE   
ValueErrorr   nowr   utc	isoformatrH   getr   updateitemsr#   r$   r%   )r)   rI   rG   rT   existinguserr"   r   r   r   upsert_admin_user   sF    *(r\   boolc                 C  sf   t | pd  }|s tdt &}|d|f}|  W d    n1 sR0    Y  t|jS )NrC   rJ   zM
            DELETE FROM admin_users
            WHERE email = ?
            )	rA   rD   rE   rS   r#   r$   r%   r]   Zrowcount)r)   rG   r"   cursorr   r   r   delete_admin_user   s    &r_   )message_typer   c                 C  s(   t | pd  }|dvr$td|S )NrC   >   noticereplyu4   message_type은 notice 또는 reply여야 합니다.)rA   rD   rE   rS   )r`   rG   r   r   r   _normalize_message_type   s    rc   rC   T)target_email	is_active)r`   titlebodyrd   re   r   c                C  sZ  t | }t|pd  }t|p$d }t|p4d }|dkrP|sPtd|dkr\d}|shtd|sttdttj	 }	t
 v}
|
d||f }|rt|d n|	}|
d	|||||rd
nd||	f |
  |
d||f }W d    n1 s0    Y  |s td|d |d |d |d |d t|d |d |d dS )NrC   rb   u1   reply 저장 시 target_email이 필요합니다.ra      제목을 입력하세요.   내용을 입력하세요.z
            SELECT created_at
            FROM admin_messages
            WHERE message_type = ? AND target_email = ?
            r.   a  
            INSERT INTO admin_messages (
                message_type, target_email, title, body, is_active, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(message_type, target_email) DO UPDATE SET
                title=excluded.title,
                body=excluded.body,
                is_active=excluded.is_active,
                updated_at=excluded.updated_at
               r   z
            SELECT id, message_type, target_email, title, body, is_active, created_at, updated_at
            FROM admin_messages
            WHERE message_type = ? AND target_email = ?
            u2   메시지 저장 후 조회에 실패했습니다.idr`   rd   rf   rg   re   r/   rk   messageTypetargetEmailrf   rg   isActiver6   r7   )rc   rA   rD   rE   rS   r   rT   r   rU   rV   r#   r$   rF   r%   RuntimeErrorr]   )r`   rf   rg   rd   re   Znormalized_typenormalized_targetnormalized_titlenormalized_bodyrT   r"   rZ   r.   r(   r   r   r   upsert_admin_message   s\    
(	
rt   )rd   r   c                 C  s   t | pd  }t 8}|d }d }|rD|d|f }W d    n1 sX0    Y  ddddd}||||d	S )
NrC   a  
            SELECT id, message_type, target_email, title, body, is_active, created_at, updated_at
            FROM admin_messages
            WHERE message_type = 'notice' AND target_email = '' AND is_active = 1
            ORDER BY updated_at DESC
            LIMIT 1
            a0  
                SELECT id, message_type, target_email, title, body, is_active, created_at, updated_at
                FROM admin_messages
                WHERE message_type = 'reply' AND target_email = ? AND is_active = 1
                ORDER BY updated_at DESC
                LIMIT 1
                zsqlite3.Row | NonerB   r'   c              	   S  sB   | sd S | d | d | d | d | d t | d | d | d d	S )
Nrk   r`   rd   rf   rg   re   r.   r/   rl   )r]   r8   r   r   r   	serializeO  s    
z$get_user_messages.<locals>.serializera   rb   )rA   rD   rE   r#   r$   rF   )rd   rq   r"   Z
notice_rowZ	reply_rowru   r   r   r   get_user_messages6  s    	&rw   )rf   rg   r   c                 C  s   t | pd }t |pd }|s,td|s8tdttj }t @}|	d||||f}|
  |	d|jf }W d    n1 s0    Y  |std|d |d |d	 |d
 |d dS )NrC   u!   공지 제목을 입력하세요.u!   공지 내용을 입력하세요.zx
            INSERT INTO notice_posts (title, body, created_at, updated_at)
            VALUES (?, ?, ?, ?)
            z
            SELECT id, title, body, created_at, updated_at
            FROM notice_posts
            WHERE id = ?
            u/   공지 저장 후 조회에 실패했습니다.rk   rf   rg   r.   r/   rk   rf   rg   r6   r7   )rA   rD   rS   r   rT   r   rU   rV   r#   r$   r%   	lastrowidrF   rp   )rf   rg   rr   rs   rT   r"   r^   r(   r   r   r   create_notice_postc  s2    
&	rz      int)limitr   c                 C  s^   t dtt| pdd}t "}|d|f }W d    n1 sF0    Y  dd |D S )Nrj   r{      z
            SELECT id, title, body, created_at, updated_at
            FROM notice_posts
            ORDER BY updated_at DESC
            LIMIT ?
            c                 S  s0   g | ](}|d  |d |d |d |d dqS )rk   rf   rg   r.   r/   rx   r   r:   r   r   r   r<     s   z%list_notice_posts.<locals>.<listcomp>)maxminr|   r#   r$   r>   )r}   
safe_limitr"   r?   r   r   r   list_notice_posts  s    &	r   )r)   sender_typerf   rg   r   c              	   C  s"  t | pd  }t |pd  }t |p0d }t |p@d }|rTd|vr\td|dvrltd|sxtd|stdttj }t	 B}	|	
d|||||f}
|	  |	
d	|
jf }W d    n1 s0    Y  |std
|d |d |d |d |d |d dS )NrC   @u.   올바른 이메일 주소가 필요합니다.>   r[   adminu4   sender_type은 user 또는 admin이어야 합니다.rh   ri   z
            INSERT INTO inquiry_messages (email, sender_type, title, body, created_at)
            VALUES (?, ?, ?, ?, ?)
            z
            SELECT id, email, sender_type, title, body, created_at
            FROM inquiry_messages
            WHERE id = ?
            u/   문의 저장 후 조회에 실패했습니다.rk   r)   r   rf   rg   r.   rk   r)   
senderTyperf   rg   r6   )rA   rD   rE   rS   r   rT   r   rU   rV   r#   r$   r%   ry   rF   rp   )r)   r   rf   rg   normalized_emailZnormalized_senderrr   rs   rT   r"   r^   r(   r   r   r   create_inquiry_message  s@    &	r   d   )rd   r}   r   c                 C  s   t | pd  }tdtt|p"dd}t <}|rN|d||f }n|d|f }W d    n1 st0    Y  dd |D S )	NrC   rj   r   i,  z
                SELECT id, email, sender_type, title, body, created_at
                FROM inquiry_messages
                WHERE email = ?
                ORDER BY created_at DESC
                LIMIT ?
                z
                SELECT id, email, sender_type, title, body, created_at
                FROM inquiry_messages
                ORDER BY created_at DESC
                LIMIT ?
                c              	   S  s6   g | ].}|d  |d |d |d |d |d dqS )rk   r)   r   rf   rg   r.   r   r   r:   r   r   r   r<     s   	z)list_inquiry_messages.<locals>.<listcomp>)	rA   rD   rE   r   r   r|   r#   r$   r>   )rd   r}   r   r   r"   r?   r   r   r   list_inquiry_messages  s    
&
	r   c                 C  s   t | pd  }tdd}|r*|d nd }d }|rVt|dd}tdd |D d }|d u rt|}|d}|d u r|d	}||d
S )NrC   rj   )r}   r   rM   c                 s  s    | ]}| d dkr|V  qdS )r   r   N)rW   )r;   itemr   r   r   	<genexpr>   r=   z1get_latest_user_message_bundle.<locals>.<genexpr>ra   rb   rv   )rA   rD   rE   r   r   nextrw   rW   )rd   r   noticesZlatest_noticeZlatest_replymessagesfallbackr   r   r   get_latest_user_message_bundle  s    


r         )
keep_startkeep_end)rQ   r   r   r   c                C  sl   t | pd } | sdS t| || kr4dt|  S | d |  dtt| | | d  | | d   S )NrC   *r   )rA   rD   lenr   )rQ   r   r   r   r   r   mask_secret  s    r   c                  C  sJ   d t tjddtjddt g} t| d	 d d S )N|ZCOMPUTERNAMErC   USERNAMEr	      )
joinplatformZnodeosenvironrW   hashlibsha256encode	hexdigest)rawr   r   r   get_machine_fingerprint  s    r   c           	      C  s6  | pt  } t| dtr$| dni }t| dp6d }t| dpLd }t| dpbd pld}t| dp|d }t| dpd }d	d
d|||dt dt|td|d}|s|S |r$i ||}||d< ||d< ||d< ||d< t |d< t||d< t|d< d|d< |S i |dddS )NlicenseStatusaccountEmailrC   	authTokenaccountStatuspendingr2   r1   unconfigureduI   대시보드에서 이메일을 1회 입력해 기기를 등록하세요.F
local_stub)r*   message	checkedAtr2   licenseer1   serverReachable	machineIddeviceBoundauthTokenMasked
appVersionmoder   r   r   r   r   r   uV   이메일 연결은 저장되었지만 아직 기기 검증을 하지 않았습니다.)r*   r   )	r   
isinstancerW   r   rA   rD   r   r   APP_VERSION)	r   saved_statusaccount_email
auth_tokenaccount_statusr,   r+   Zbase_statusZmergedr   r   r   build_license_status#  sP    

r   c              
   C  s  t | dpd }t | dp$d }t | dp:d }t }|rTt|nd }|rt |dpv| dpvd| d< t |dp| dpd| d< t |d	p| d	pd
| d	< t |dpd rt |dpd | d< t | dpd }t|  t | dpd p*d}t | dp<d }t | d	pTd
 p`d
}ttj	}	|rz|sdd|	
 d||dddd	S |sdd|	
 d||dd|d	S |	tdd }
|rzt|dd}
W n" ty   |	tdd }
Y n0 |
|	kr0dd|	
 |

 ||dddd	S |dkrZdd|	
 |

 ||dd|d	S ||krdd|	
 |

 ||dd|d	S dd|	
 |

 ||dd|d	S )Nr   rC   r   boundFingerprintr*   r   rK   r2   r1   rL   r3   r   u>   먼저 이메일로 로그인해 현재 PC를 등록하세요.Fr   	r*   r   r   r2   r   r1   r   r   r   uC   기기 바인딩 정보가 없어 다시 로그인해야 합니다.TrM   rN   Zz+00:00erroruY   사용 기간이 만료되었습니다. 운영자에게 연장 요청이 필요합니다.Zexpiredu<   현재 계정 상태가 활성화되어 있지 않습니다.up   등록된 PC 정보와 현재 기기 정보가 다릅니다. 1계정 1PC 정책으로 실행이 제한됩니다.u=   저장된 계정과 현재 PC fingerprint가 일치합니다.)rA   rW   rD   r   rH   r   r   rT   r   rU   rV   r   fromisoformatreplacerS   )r   r   r   Zbound_fingerprintZcurrent_fingerprint
admin_userr   Zexpires_at_rawr+   rT   r,   r   r   r   perform_license_checkS  s       


r   )r   r*   r   c                 C  s>   i |t  tt| dpdtdd}|| d< t|  |S )Nr   rC   r   )r   r   r   r   r   )r   r   rA   rW   r   r   )r   r*   Zmerged_statusr   r   r   update_license_status  s    r   ztuple[dict, dict]c           	      C  s  t | pd  }|r d|vr(tdt }t }t |dpBd  }t |dp\d }|rx||krxtd|r||krtdt	t
j }t||||d}||d< t |d	ptd
|d	< ||d< t |dp||d< t |dpd|d< t |dp,t	t
jtdd  |d< t |dpDd|d< t|dd|t |dpfd|t |dpzdddt |dpdd	}||fS )NrC   r   u.   올바른 이메일 주소를 입력하세요.r   r   uh   이미 다른 이메일이 이 PC에 연결되어 있습니다. 계정 변경 정책이 필요합니다.u.   이미 다른 PC에 연결된 토큰입니다.)r3   r4   r5   r   r   r4   boundAtr*   rK   r   r2   rM   rN   r1   rL   uJ   이메일 1회 로그인 후 현재 PC가 계정에 연결되었습니다.Tr   )rA   rD   rE   rS   r   r   rW   rp   r   rT   r   rU   rV   r\   secretsZ	token_hexr   r   )	r)   r   Z
machine_idr   Zexisting_emailZexisting_fingerprintZcurrent_timer   r*   r   r   r   perform_email_login  sP    	.r   )N)rC   )r{   )rC   r   )rC   )N)+
__future__r   r   r   r   r   r   r   r   r   r   pathlibr   __file__resolveparentROOT_DIRr   r    r   r   r   r#   r&   r9   r@   rH   r\   r_   rc   rt   rw   rz   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>   sJ   
;;Q-'/'	0i