feat(server): modularize backend and admin console
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import secrets
|
||||
|
||||
import db
|
||||
|
||||
|
||||
def get_device(device_id: str):
|
||||
conn = db.get_conn()
|
||||
row = conn.execute("SELECT * FROM devices WHERE device_id=?", (device_id,)).fetchone()
|
||||
conn.close()
|
||||
return row
|
||||
|
||||
|
||||
def get_license(license_id: str):
|
||||
conn = db.get_conn()
|
||||
row = conn.execute("SELECT * FROM licenses WHERE license_id=?", (license_id,)).fetchone()
|
||||
conn.close()
|
||||
return row
|
||||
|
||||
|
||||
def touch_device(device_id: str):
|
||||
conn = db.get_conn()
|
||||
conn.execute("UPDATE devices SET last_seen_at=datetime('now') WHERE device_id=?", (device_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def issue_device(app_id: str, channel: str, license_key_hash: str, installation_id: str, machine_hash: str, ip: str):
|
||||
now = datetime.now(timezone.utc)
|
||||
conn = db.get_conn()
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
channel_row = conn.execute("SELECT * FROM channels WHERE app_id=? AND channel_code=?", (app_id, channel)).fetchone()
|
||||
if not channel_row:
|
||||
conn.rollback()
|
||||
return {"status": "channel_not_found"}
|
||||
if not channel_row["enabled"]:
|
||||
conn.rollback()
|
||||
return {"status": "channel_disabled"}
|
||||
license_row = conn.execute("SELECT * FROM licenses WHERE license_key_hash=?", (license_key_hash,)).fetchone()
|
||||
try:
|
||||
expiry = datetime.fromisoformat((license_row["valid_until"] if license_row else "").replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
expiry = now - timedelta(seconds=1)
|
||||
if (
|
||||
not license_row
|
||||
or license_row["status"] != "active"
|
||||
or expiry <= now
|
||||
or license_row["app_id"] != app_id
|
||||
or license_row["channel_code"] != channel
|
||||
):
|
||||
conn.rollback()
|
||||
return {"status": "license_invalid"}
|
||||
|
||||
device_row = conn.execute("SELECT * FROM devices WHERE app_id=? AND installation_id=?", (app_id, installation_id)).fetchone()
|
||||
if device_row and device_row["disabled"]:
|
||||
conn.rollback()
|
||||
return {"status": "device_disabled", "reason": device_row["disabled_reason"] or "设备已被禁用"}
|
||||
if device_row and device_row["license_id"] not in ("", license_row["license_id"]):
|
||||
conn.rollback()
|
||||
return {"status": "license_conflict"}
|
||||
|
||||
device_id = device_row["device_id"] if device_row else "dev_" + secrets.token_hex(16)
|
||||
seq = int(device_row["credential_seq"]) if device_row else 1
|
||||
bound = conn.execute("SELECT 1 FROM license_devices WHERE license_id=? AND device_id=?", (license_row["license_id"], device_id)).fetchone()
|
||||
used = conn.execute("SELECT COUNT(*) FROM license_devices WHERE license_id=?", (license_row["license_id"],)).fetchone()[0]
|
||||
if not bound and used >= int(license_row["max_devices"]):
|
||||
conn.rollback()
|
||||
return {"status": "license_device_limit"}
|
||||
|
||||
if device_row:
|
||||
conn.execute(
|
||||
"UPDATE devices SET machine_hash=?,license_id=?,channel=?,last_seen_at=datetime('now'),last_ip=? WHERE device_id=?",
|
||||
(machine_hash, license_row["license_id"], channel, ip, device_id),
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
"INSERT INTO devices(device_id,app_id,installation_id,machine_hash,credential_seq,last_ip,license_id,channel) VALUES(?,?,?,?,?,?,?,?)",
|
||||
(device_id, app_id, installation_id, machine_hash, seq, ip, license_row["license_id"], channel),
|
||||
)
|
||||
conn.execute("INSERT OR IGNORE INTO license_devices(license_id,device_id) VALUES(?,?)", (license_row["license_id"], device_id))
|
||||
conn.commit()
|
||||
return {"status": "ok", "device_id": device_id, "license": license_row, "credential_seq": seq, "issued_at": now}
|
||||
except Exception:
|
||||
conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_update_context(app_id: str, channel: str):
|
||||
conn = db.get_conn()
|
||||
try:
|
||||
channel_row = conn.execute("SELECT * FROM channels WHERE app_id=? AND channel_code=?", (app_id, channel)).fetchone()
|
||||
if not channel_row:
|
||||
return {"status": "channel_not_found"}
|
||||
if not channel_row["enabled"]:
|
||||
return {"status": "channel_disabled"}
|
||||
version = conn.execute(
|
||||
"SELECT * FROM versions WHERE app_id=? AND channel=? AND latest=1",
|
||||
(app_id, channel),
|
||||
).fetchone()
|
||||
policy = conn.execute(
|
||||
"SELECT * FROM version_policies WHERE app_id=? AND channel=?",
|
||||
(app_id, channel),
|
||||
).fetchone()
|
||||
return {"status": "ok", "version": version, "policy": policy}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_download_files(version_id: int, app_id: str, channel: str, version: str, identity: dict, ip: str, user_agent: str):
|
||||
conn = db.get_conn()
|
||||
version_row = conn.execute("SELECT app_id,channel,version FROM versions WHERE id=?", (version_id,)).fetchone()
|
||||
if not version_row or version_row["app_id"] != app_id or version_row["channel"] != channel or version_row["version"] != version:
|
||||
conn.close()
|
||||
return {"status": "not_found"}
|
||||
rows = conn.execute("SELECT path, sha256, size FROM version_files WHERE version_id=?", (version_id,)).fetchall()
|
||||
conn.executemany(
|
||||
"""INSERT INTO download_logs(device_id,license_id,app_id,version,channel_code,file_path,file_size,result,ip,user_agent)
|
||||
VALUES(?,?,?,?,?,?,?,?,?,?)""",
|
||||
[
|
||||
(identity["device_id"], identity["license_id"], app_id, version, channel, row["path"], row["size"], "authorized", ip, user_agent)
|
||||
for row in rows
|
||||
],
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"status": "ok", "files": rows}
|
||||
|
||||
|
||||
def insert_download_report_logs(values: list[tuple]) -> int:
|
||||
conn = db.get_conn()
|
||||
if values:
|
||||
conn.executemany(
|
||||
"INSERT INTO download_logs(device_id,license_id,app_id,version,channel_code,file_path,file_size,result,ip,user_agent) VALUES(?,?,?,?,?,?,?,?,?,?)",
|
||||
values,
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return len(values)
|
||||
|
||||
|
||||
def get_manifest_version_files(version_id: int):
|
||||
conn = db.get_conn()
|
||||
version = conn.execute(
|
||||
"SELECT app_id, channel, version, create_time FROM versions WHERE id=?",
|
||||
(version_id,),
|
||||
).fetchone()
|
||||
if not version:
|
||||
conn.close()
|
||||
return None, []
|
||||
rows = conn.execute("SELECT path, sha256, size FROM version_files WHERE version_id=?", (version_id,)).fetchall()
|
||||
conn.close()
|
||||
return version, rows
|
||||
|
||||
|
||||
def insert_upgrade_log(device_id: str, from_version: str, to_version: str, result: str):
|
||||
conn = db.get_conn()
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO upgrade_logs(device_id,from_ver,to_ver,result,create_time)
|
||||
VALUES (?,?,?,?,datetime('now'))
|
||||
""",
|
||||
(device_id, from_version, to_version, result),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user