Issue invite configs as subscription URLs with redeem-based lifetime.

Admin picks inbound once, sets duration in days from Get config, and configures the 3x-ui /sub base URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-26 01:34:40 +03:00
co-authored by Cursor
parent 7b3d8aac34
commit 7d7094bc38
13 changed files with 347 additions and 148 deletions
+3
View File
@@ -84,6 +84,9 @@ def init_schema():
cur.execute(
"CREATE INDEX IF NOT EXISTS idx_users_xui_email ON users(xui_email)"
)
cur.execute(
"ALTER TABLE invite_links ADD COLUMN IF NOT EXISTS duration_days INTEGER NOT NULL DEFAULT 0"
)
conn.commit()
_schema_ready = True
logger.info('PostgreSQL schema ready')
+1
View File
@@ -85,6 +85,7 @@ CREATE TABLE IF NOT EXISTS invite_links (
xui_inbound_id INTEGER NOT NULL DEFAULT 0,
password_hash TEXT,
expires_at TIMESTAMPTZ,
duration_days INTEGER NOT NULL DEFAULT 0,
note TEXT,
created_at TIMESTAMPTZ
);
+6 -3
View File
@@ -45,6 +45,7 @@ DEFAULT_SETTINGS = {
'xui_server_id': 0,
'xui_protocol': 'xray',
'xui_inbound_id': 0,
'xui_sub_url': '',
},
'guest': {
'enabled': False,
@@ -209,6 +210,7 @@ def _row_to_invite(row) -> dict:
'xui_inbound_id': int(row['xui_inbound_id'] or 0),
'password_hash': row['password_hash'],
'expires_at': _ts_iso(row['expires_at']),
'duration_days': int(row.get('duration_days') or 0),
'note': row['note'] or '',
'created_at': _ts_iso(row['created_at']),
}
@@ -252,7 +254,7 @@ def load_data() -> dict:
cur.execute(
'SELECT id, name, token, enabled, max_uses, used_count, user_id, '
'protocol, server_id, xui_inbound_id, password_hash, expires_at, '
'note, created_at FROM invite_links '
'duration_days, note, created_at FROM invite_links '
'ORDER BY created_at DESC NULLS LAST, name'
)
invite_links = [_row_to_invite(r) for r in cur.fetchall()]
@@ -392,8 +394,8 @@ def save_data(data: dict) -> None:
'INSERT INTO invite_links ('
'id, name, token, enabled, max_uses, used_count, user_id, '
'protocol, server_id, xui_inbound_id, password_hash, expires_at, '
'note, created_at'
') VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
'duration_days, note, created_at'
') VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
(
_as_uuid(link['id']),
link.get('name') or '',
@@ -407,6 +409,7 @@ def save_data(data: dict) -> None:
int(link.get('xui_inbound_id') or 0),
link.get('password_hash'),
_parse_ts(link.get('expires_at')),
int(link.get('duration_days') or 0),
link.get('note') or '',
_parse_ts(link.get('created_at')),
),