modified: web/blueprints/site_admin.py

modified:   web/panel_db.py
	modified:   web/templates/admin/user_edit.html
	modified:   web/templates/admin/users.html
This commit is contained in:
SimolZimol
2026-04-13 18:25:09 +02:00
parent 31b45d4db4
commit fe2e5e3c9c
4 changed files with 323 additions and 30 deletions

View File

@@ -103,7 +103,8 @@ PANEL_SCHEMA = [
"""CREATE TABLE IF NOT EXISTS group_invites (
id INT AUTO_INCREMENT PRIMARY KEY,
group_id INT NOT NULL,
group_id INT NULL,
is_site_admin TINYINT(1) NOT NULL DEFAULT 0,
invited_username VARCHAR(50) NOT NULL,
invited_email VARCHAR(255) NOT NULL,
role ENUM('group_owner','group_admin','moderator','viewer','auditor','admin','member') DEFAULT 'viewer',
@@ -115,7 +116,6 @@ PANEL_SCHEMA = [
send_count INT NOT NULL DEFAULT 0,
accepted_at DATETIME NULL,
revoked_at DATETIME NULL,
UNIQUE KEY uq_group_pending_invite_email (group_id, invited_email, revoked_at, accepted_at),
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE,
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""",
@@ -176,6 +176,16 @@ def init_databases():
cur.execute("ALTER TABLE group_invites ADD COLUMN send_count INT NOT NULL DEFAULT 0")
except Exception:
pass
try:
cur.execute("SET foreign_key_checks=0")
cur.execute("ALTER TABLE group_invites MODIFY group_id INT NULL")
cur.execute("SET foreign_key_checks=1")
except Exception:
pass
try:
cur.execute("ALTER TABLE group_invites ADD COLUMN is_site_admin TINYINT(1) NOT NULL DEFAULT 0")
except Exception:
pass
finally:
panel.close()
@@ -229,13 +239,13 @@ def create_user_for_group(username: str, email: str, password: str, group_id: in
conn.close()
def create_group_invite(group_id: int, username: str, email: str, role: str, created_by_user_id: int) -> str:
def create_group_invite(group_id, username: str, email: str, role: str, created_by_user_id: int, is_site_admin: bool = False) -> str:
expires_at = datetime.utcnow() + timedelta(hours=Config.INVITE_EXPIRY_HOURS)
token = secrets.token_urlsafe(32)
_panel_query(
"INSERT INTO group_invites (group_id, invited_username, invited_email, role, token, created_by_user_id, expires_at, last_sent_at, send_count) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,NULL,0)",
(group_id, username, email, role, token, created_by_user_id, expires_at),
"INSERT INTO group_invites (group_id, invited_username, invited_email, role, token, created_by_user_id, expires_at, last_sent_at, send_count, is_site_admin) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,NULL,0,%s)",
(group_id, username, email, role, token, created_by_user_id, expires_at, int(is_site_admin)),
write=True,
)
return token
@@ -291,7 +301,7 @@ def get_invite_by_token(token: str):
return _panel_query(
"SELECT gi.*, g.name AS group_name, u.username AS created_by_username "
"FROM group_invites gi "
"JOIN user_groups g ON g.id = gi.group_id "
"LEFT JOIN user_groups g ON g.id = gi.group_id "
"JOIN users u ON u.id = gi.created_by_user_id "
"WHERE gi.token=%s",
(token,),
@@ -307,6 +317,64 @@ def revoke_group_invite(invite_id: int, group_id: int):
)
def list_all_active_invites():
"""All pending invites across every group (for site admin users page)."""
return _panel_query(
"SELECT gi.*, g.name AS group_name, u.username AS created_by_username "
"FROM group_invites gi "
"LEFT JOIN user_groups g ON g.id = gi.group_id "
"JOIN users u ON u.id = gi.created_by_user_id "
"WHERE gi.accepted_at IS NULL AND gi.revoked_at IS NULL AND gi.expires_at > UTC_TIMESTAMP() "
"ORDER BY gi.created_at DESC"
)
def get_invite_by_id_global(invite_id: int):
return _panel_query(
"SELECT gi.*, g.name AS group_name, u.username AS created_by_username "
"FROM group_invites gi "
"LEFT JOIN user_groups g ON g.id = gi.group_id "
"JOIN users u ON u.id = gi.created_by_user_id "
"WHERE gi.id=%s",
(invite_id,),
fetchone=True,
)
def revoke_invite_global(invite_id: int):
_panel_query(
"UPDATE group_invites SET revoked_at=UTC_TIMESTAMP() WHERE id=%s AND accepted_at IS NULL AND revoked_at IS NULL",
(invite_id,),
write=True,
)
def mark_invite_sent_global(invite_id: int):
_panel_query(
"UPDATE group_invites SET last_sent_at=UTC_TIMESTAMP(), send_count=send_count+1 WHERE id=%s",
(invite_id,),
write=True,
)
def get_active_invite_by_email_global(email: str):
return _panel_query(
"SELECT * FROM group_invites WHERE invited_email=%s "
"AND accepted_at IS NULL AND revoked_at IS NULL AND expires_at > UTC_TIMESTAMP()",
(email,),
fetchone=True,
)
def get_active_invite_by_username_global(username: str):
return _panel_query(
"SELECT * FROM group_invites WHERE invited_username=%s "
"AND accepted_at IS NULL AND revoked_at IS NULL AND expires_at > UTC_TIMESTAMP()",
(username,),
fetchone=True,
)
def mark_group_invite_sent(invite_id: int, group_id: int):
_panel_query(
"UPDATE group_invites SET last_sent_at=UTC_TIMESTAMP(), send_count=send_count+1 WHERE id=%s AND group_id=%s",
@@ -342,10 +410,16 @@ def accept_group_invite(token: str, password: str) -> dict | None:
(invite["invited_username"], invite["invited_email"], pw_hash, salt, 0),
)
user_id = cur.lastrowid
site_admin_flag = int(bool(invite.get("is_site_admin")))
cur.execute(
"INSERT INTO group_members (user_id, group_id, role, permissions) VALUES (%s,%s,%s,%s)",
(user_id, invite["group_id"], invite["role"], json.dumps(permissions)),
"UPDATE users SET is_site_admin=%s WHERE id=%s",
(site_admin_flag, user_id),
)
if invite["group_id"] is not None:
cur.execute(
"INSERT INTO group_members (user_id, group_id, role, permissions) VALUES (%s,%s,%s,%s)",
(user_id, invite["group_id"], invite["role"], json.dumps(permissions)),
)
cur.execute(
"UPDATE group_invites SET accepted_at=UTC_TIMESTAMP() WHERE id=%s AND accepted_at IS NULL AND revoked_at IS NULL",
(invite["id"],),