diff --git a/app.py b/app.py index 8f35a90..3ea57a8 100644 --- a/app.py +++ b/app.py @@ -597,11 +597,53 @@ MESSAGE_SELECT = """ def team_page_select() -> str: + has_parent_page = False + try: + with get_db() as db: + has_parent_page = column_exists(db, "pages", "parent_page_id") + except Exception: + has_parent_page = False + + if has_parent_page: + return """ + SELECT + p.id, + p.team_id, + p.channel_id, + p.parent_page_id, + parent.slug AS parent_page_slug, + parent.title AS parent_page_title, + p.slug, + p.title, + p.body, + p.visibility, + p.page_type, + p.created_by_user_id, + p.updated_by_user_id, + p.created_at, + p.updated_at, + p.is_active, + t.slug AS team_slug, + t.name AS team_name, + c.slug AS channel_slug, + c.name AS channel_name + FROM pages AS p + JOIN teams AS t + ON t.id = p.team_id + LEFT JOIN channels AS c + ON c.id = p.channel_id + LEFT JOIN pages AS parent + ON parent.id = p.parent_page_id + """ + return """ SELECT p.id, p.team_id, p.channel_id, + NULL AS parent_page_id, + NULL AS parent_page_slug, + NULL AS parent_page_title, p.slug, p.title, p.body, @@ -857,6 +899,7 @@ def user_payload(row: sqlite3.Row, settings_row: sqlite3.Row | None = None) -> d "restricted_until": row["restricted_until"], } + def current_user() -> Optional[dict]: user_id = session.get("user_id") if not user_id: @@ -950,7 +993,7 @@ def require_writable_user(): def require_moderator(): user, error = require_writable_user() if error: - return None, error + return error[0] if isinstance(error, tuple) and len(error) == 2 else error if user["role"] not in {"moderator", "admin"}: return None, (jsonify({"error": "Moderator access required.", "code": "forbidden"}), 403) return user, None @@ -1320,8 +1363,11 @@ def init_db() -> None: bio_mode TEXT NOT NULL DEFAULT 'txt', website TEXT, location TEXT, + status_message TEXT NOT NULL DEFAULT '', + banner_attachment_id INTEGER, updated_at TEXT NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (banner_attachment_id) REFERENCES attachments(id) ON DELETE SET NULL ) """ ) @@ -1334,6 +1380,13 @@ def init_db() -> None: name_color TEXT, custom_css TEXT NOT NULL DEFAULT '', theme_mode TEXT NOT NULL DEFAULT 'dark', + profile_background_color TEXT, + profile_banner_color TEXT, + profile_visibility TEXT NOT NULL DEFAULT 'public', + last_active_visibility TEXT NOT NULL DEFAULT 'everyone', + presence_mode TEXT NOT NULL DEFAULT 'online', + dm_layout_mode TEXT NOT NULL DEFAULT 'cards', + show_presence_badge INTEGER NOT NULL DEFAULT 1, updated_at TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) @@ -1525,12 +1578,33 @@ def init_db() -> None: db.execute("ALTER TABLE messages ADD COLUMN channel_id INTEGER") if not column_exists(db, "messages", "direct_conversation_id"): db.execute("ALTER TABLE messages ADD COLUMN direct_conversation_id INTEGER") - if not column_exists(db, "user_settings", "name_color"): - db.execute("ALTER TABLE user_settings ADD COLUMN name_color TEXT") - if not column_exists(db, "presence_events", "direct_conversation_id"): - db.execute("ALTER TABLE presence_events ADD COLUMN direct_conversation_id INTEGER") + if not column_exists(db, "user_profiles", "badge_text"): db.execute("ALTER TABLE user_profiles ADD COLUMN badge_text TEXT") + if not column_exists(db, "user_profiles", "status_message"): + db.execute("ALTER TABLE user_profiles ADD COLUMN status_message TEXT NOT NULL DEFAULT ''") + if not column_exists(db, "user_profiles", "banner_attachment_id"): + db.execute("ALTER TABLE user_profiles ADD COLUMN banner_attachment_id INTEGER") + + if not column_exists(db, "user_settings", "name_color"): + db.execute("ALTER TABLE user_settings ADD COLUMN name_color TEXT") + if not column_exists(db, "user_settings", "profile_background_color"): + db.execute("ALTER TABLE user_settings ADD COLUMN profile_background_color TEXT") + if not column_exists(db, "user_settings", "profile_banner_color"): + db.execute("ALTER TABLE user_settings ADD COLUMN profile_banner_color TEXT") + if not column_exists(db, "user_settings", "profile_visibility"): + db.execute("ALTER TABLE user_settings ADD COLUMN profile_visibility TEXT NOT NULL DEFAULT 'public'") + if not column_exists(db, "user_settings", "last_active_visibility"): + db.execute("ALTER TABLE user_settings ADD COLUMN last_active_visibility TEXT NOT NULL DEFAULT 'everyone'") + if not column_exists(db, "user_settings", "presence_mode"): + db.execute("ALTER TABLE user_settings ADD COLUMN presence_mode TEXT NOT NULL DEFAULT 'online'") + if not column_exists(db, "user_settings", "dm_layout_mode"): + db.execute("ALTER TABLE user_settings ADD COLUMN dm_layout_mode TEXT NOT NULL DEFAULT 'cards'") + if not column_exists(db, "user_settings", "show_presence_badge"): + db.execute("ALTER TABLE user_settings ADD COLUMN show_presence_badge INTEGER NOT NULL DEFAULT 1") + + if not column_exists(db, "presence_events", "direct_conversation_id"): + db.execute("ALTER TABLE presence_events ADD COLUMN direct_conversation_id INTEGER") db.execute("CREATE INDEX IF NOT EXISTS idx_messages_room_id_id ON messages(room_id, id)") db.execute("CREATE INDEX IF NOT EXISTS idx_messages_channel_id_id ON messages(channel_id, id)") @@ -1722,6 +1796,8 @@ def init_db() -> None: bio_mode, website, location, + status_message, + banner_attachment_id, updated_at ) SELECT @@ -1732,6 +1808,8 @@ def init_db() -> None: 'txt', '', '', + '', + NULL, ? FROM users """, @@ -1746,6 +1824,13 @@ def init_db() -> None: name_color, custom_css, theme_mode, + profile_background_color, + profile_banner_color, + profile_visibility, + last_active_visibility, + presence_mode, + dm_layout_mode, + show_presence_badge, updated_at ) SELECT @@ -1754,6 +1839,13 @@ def init_db() -> None: NULL, '', 'dark', + NULL, + NULL, + 'public', + 'everyone', + 'online', + 'cards', + 1, ? FROM users """, @@ -1796,7 +1888,7 @@ def bootstrap_admin() -> None: return if validate_username(username) or validate_password(password, username): - print("WARNING: Admin bootstrap credentials failed validation.") + print("warning: admin bootstrap credentials failed validation.") return with get_db() as db: @@ -1846,8 +1938,10 @@ def bootstrap_admin() -> None: bio_mode, website, location, + status_message, + banner_attachment_id, updated_at - ) VALUES (?, ?, '', '', 'txt', '', '', ?) + ) VALUES (?, ?, '', '', 'txt', '', '', '', NULL, ?) """, (user_id, username, now_iso()), ) @@ -1860,8 +1954,15 @@ def bootstrap_admin() -> None: name_color, custom_css, theme_mode, + profile_background_color, + profile_banner_color, + profile_visibility, + last_active_visibility, + presence_mode, + dm_layout_mode, + show_presence_badge, updated_at - ) VALUES (?, '#f7dce8', NULL, '', 'dark', ?) + ) VALUES (?, '#f7dce8', NULL, '', 'dark', NULL, NULL, 'public', 'everyone', 'online', 'cards', 1, ?) """, (user_id, now_iso()), ) @@ -1893,7 +1994,6 @@ def api_me(): @app.route("/api/register", methods=["POST"]) def register(): - limited = rate_limited("register") if limited: return limited @@ -1957,8 +2057,10 @@ def register(): bio_mode, website, location, + status_message, + banner_attachment_id, updated_at - ) VALUES (?, ?, '', '', 'txt', '', '', ?) + ) VALUES (?, ?, '', '', 'txt', '', '', '', NULL, ?) """, (user["id"], username, now_iso()), ) @@ -1971,8 +2073,15 @@ def register(): name_color, custom_css, theme_mode, + profile_background_color, + profile_banner_color, + profile_visibility, + last_active_visibility, + presence_mode, + dm_layout_mode, + show_presence_badge, updated_at - ) VALUES (?, '#f7dce8', NULL, '', 'dark', ?) + ) VALUES (?, '#f7dce8', NULL, '', 'dark', NULL, NULL, 'public', 'everyone', 'online', 'cards', 1, ?) """, (user["id"], now_iso()), )