Add PostgreSQL-backed DuckMail GUI
This commit is contained in:
157
account_store.py
Normal file
157
account_store.py
Normal file
@@ -0,0 +1,157 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
|
||||
try:
|
||||
import psycopg
|
||||
except ImportError:
|
||||
psycopg = None
|
||||
|
||||
try:
|
||||
import psycopg2
|
||||
except ImportError:
|
||||
psycopg2 = None
|
||||
|
||||
|
||||
_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def _safe_read_json(path):
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _safe_write_json(path, data):
|
||||
tmp_path = f"{path}.tmp"
|
||||
with open(tmp_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp_path, path)
|
||||
|
||||
|
||||
def save_config(path, config):
|
||||
with _LOCK:
|
||||
_safe_write_json(path, config)
|
||||
|
||||
|
||||
def load_config(path):
|
||||
data = _safe_read_json(path)
|
||||
if not isinstance(data, dict):
|
||||
return {}
|
||||
return data
|
||||
|
||||
|
||||
def _to_text(value):
|
||||
if value is None:
|
||||
return ""
|
||||
return str(value)
|
||||
|
||||
|
||||
def _normalize_optional_text(value):
|
||||
text = _to_text(value).strip()
|
||||
return text or None
|
||||
|
||||
|
||||
def _get_pg_driver():
|
||||
if psycopg is not None:
|
||||
return "psycopg"
|
||||
if psycopg2 is not None:
|
||||
return "psycopg2"
|
||||
raise RuntimeError("未安装 PostgreSQL 驱动,请先安装 psycopg[binary] 或 psycopg2-binary")
|
||||
|
||||
|
||||
def _get_pg_connection(config):
|
||||
driver = _get_pg_driver()
|
||||
host = str(config.get("pg_host", "")).strip()
|
||||
port = int(config.get("pg_port", 5432) or 5432)
|
||||
dbname = str(config.get("pg_db", "")).strip()
|
||||
user = str(config.get("pg_user", "")).strip()
|
||||
password = str(config.get("pg_password", "")).strip()
|
||||
connect_timeout = int(config.get("pg_connect_timeout", 10) or 10)
|
||||
|
||||
if not host or not dbname or not user:
|
||||
raise ValueError("PostgreSQL 配置不完整,请填写 Host、DB、User")
|
||||
|
||||
kwargs = {
|
||||
"host": host,
|
||||
"port": port,
|
||||
"dbname": dbname,
|
||||
"user": user,
|
||||
"password": password,
|
||||
"connect_timeout": connect_timeout,
|
||||
}
|
||||
|
||||
if driver == "psycopg":
|
||||
return psycopg.connect(**kwargs)
|
||||
return psycopg2.connect(**kwargs)
|
||||
|
||||
|
||||
def load_accounts(config):
|
||||
query = (
|
||||
"select email, mail_password, mail_token, chatgpt_password, name, birthdate, created_at "
|
||||
"from registered_accounts order by created_at desc nulls last, email asc"
|
||||
)
|
||||
accounts = []
|
||||
|
||||
with _get_pg_connection(config) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query)
|
||||
rows = cur.fetchall()
|
||||
|
||||
for row in rows:
|
||||
email, mail_password, mail_token, chatgpt_password, name, birthdate, created_at = row
|
||||
if not email:
|
||||
continue
|
||||
accounts.append(
|
||||
{
|
||||
"email": _to_text(email).strip(),
|
||||
"mail_password": _to_text(mail_password).strip(),
|
||||
"mail_token": _to_text(mail_token).strip(),
|
||||
"chatgpt_password": _to_text(chatgpt_password).strip(),
|
||||
"name": _to_text(name).strip(),
|
||||
"birthdate": _to_text(birthdate).strip(),
|
||||
"created_at": _to_text(created_at).strip(),
|
||||
"source": "postgres",
|
||||
}
|
||||
)
|
||||
return accounts
|
||||
|
||||
|
||||
def save_account(
|
||||
config,
|
||||
email,
|
||||
mail_password,
|
||||
mail_token,
|
||||
chatgpt_password=None,
|
||||
name=None,
|
||||
birthdate=None,
|
||||
):
|
||||
query = (
|
||||
"insert into registered_accounts "
|
||||
"(email, mail_password, mail_token, chatgpt_password, name, birthdate) "
|
||||
"values (%s, %s, %s, %s, %s, %s) "
|
||||
"on conflict (email) do update set "
|
||||
"mail_password = excluded.mail_password, "
|
||||
"mail_token = excluded.mail_token, "
|
||||
"chatgpt_password = excluded.chatgpt_password, "
|
||||
"name = excluded.name, "
|
||||
"birthdate = excluded.birthdate"
|
||||
)
|
||||
params = (
|
||||
email,
|
||||
mail_password,
|
||||
mail_token,
|
||||
_normalize_optional_text(chatgpt_password),
|
||||
_normalize_optional_text(name),
|
||||
_normalize_optional_text(birthdate),
|
||||
)
|
||||
|
||||
with _LOCK:
|
||||
with _get_pg_connection(config) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, params)
|
||||
conn.commit()
|
||||
Reference in New Issue
Block a user