Template
Add 3x-ui user sync via panel API (login or Bearer token).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -78,5 +78,15 @@ def init_schema():
|
||||
if stmt:
|
||||
cur.execute(stmt)
|
||||
conn.commit()
|
||||
# Soft migrations for existing databases
|
||||
with pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"ALTER TABLE users ADD COLUMN IF NOT EXISTS xui_email TEXT"
|
||||
)
|
||||
cur.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_users_xui_email ON users(xui_email)"
|
||||
)
|
||||
conn.commit()
|
||||
_schema_ready = True
|
||||
logger.info('PostgreSQL schema ready')
|
||||
|
||||
@@ -29,11 +29,14 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
last_reset_at TIMESTAMPTZ,
|
||||
expiration_date TIMESTAMPTZ,
|
||||
remnawave_uuid TEXT,
|
||||
xui_email TEXT,
|
||||
share_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
share_token TEXT,
|
||||
share_password_hash TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_xui_email ON users(xui_email);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_connections (
|
||||
id UUID PRIMARY KEY,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
||||
+14
-3
@@ -35,6 +35,15 @@ DEFAULT_SETTINGS = {
|
||||
'remnawave_create_conns': False,
|
||||
'remnawave_server_id': 0,
|
||||
'remnawave_protocol': 'awg',
|
||||
'xui_sync': False,
|
||||
'xui_url': '',
|
||||
'xui_username': '',
|
||||
'xui_password': '',
|
||||
'xui_api_token': '',
|
||||
'xui_sync_users': False,
|
||||
'xui_create_conns': False,
|
||||
'xui_server_id': 0,
|
||||
'xui_protocol': 'xray',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -116,6 +125,7 @@ def _row_to_user(row) -> dict:
|
||||
'last_reset_at': _ts_iso(row['last_reset_at']),
|
||||
'expiration_date': _ts_iso(row['expiration_date']),
|
||||
'remnawave_uuid': row['remnawave_uuid'],
|
||||
'xui_email': row.get('xui_email'),
|
||||
'share_enabled': bool(row['share_enabled']),
|
||||
'share_token': row['share_token'],
|
||||
'share_password_hash': row['share_password_hash'],
|
||||
@@ -163,7 +173,7 @@ def load_data() -> dict:
|
||||
'SELECT id, username, password_hash, role, enabled, created_at, '
|
||||
'telegram_id, email, description, traffic_limit, traffic_used, '
|
||||
'traffic_total, traffic_reset_strategy, last_reset_at, '
|
||||
'expiration_date, remnawave_uuid, share_enabled, share_token, '
|
||||
'expiration_date, remnawave_uuid, xui_email, share_enabled, share_token, '
|
||||
'share_password_hash FROM users ORDER BY created_at NULLS LAST, username'
|
||||
)
|
||||
users = [_row_to_user(r) for r in cur.fetchall()]
|
||||
@@ -242,10 +252,10 @@ def save_data(data: dict) -> None:
|
||||
'id, username, password_hash, role, enabled, created_at, '
|
||||
'telegram_id, email, description, traffic_limit, traffic_used, '
|
||||
'traffic_total, traffic_reset_strategy, last_reset_at, '
|
||||
'expiration_date, remnawave_uuid, share_enabled, share_token, '
|
||||
'expiration_date, remnawave_uuid, xui_email, share_enabled, share_token, '
|
||||
'share_password_hash'
|
||||
') VALUES ('
|
||||
'%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s'
|
||||
'%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s'
|
||||
')',
|
||||
(
|
||||
_as_uuid(user['id']),
|
||||
@@ -264,6 +274,7 @@ def save_data(data: dict) -> None:
|
||||
_parse_ts(user.get('last_reset_at')),
|
||||
_parse_ts(user.get('expiration_date')),
|
||||
user.get('remnawave_uuid'),
|
||||
user.get('xui_email'),
|
||||
bool(user.get('share_enabled', False)),
|
||||
user.get('share_token'),
|
||||
user.get('share_password_hash'),
|
||||
|
||||
Reference in New Issue
Block a user