# retoor import logging from molodetz import config from molodetz.constants import SETTINGS_DEFAULTS from molodetz.database.core import db, generate_uid, now_iso logger = logging.getLogger(__name__) TABLE_EXAMPLES = { "users": { "uid": "x", "username": "x", "email": "x@x.nl", "password_hash": "x", "role": "Member", "api_key": "x", "bio": "", "avatar_seed": None, "is_active": True, "last_seen_at": None, "suspended_until": None, "suspend_reason": None, "terms_version": "1", "created_at": now_iso(), "updated_at": now_iso(), "deleted_at": None, "deleted_by": None, }, "sessions": { "uid": "x", "token": "x", "user_uid": "x", "created_at": now_iso(), "expires_at": now_iso(), "deleted_at": None, "deleted_by": None, }, "posts": { "uid": "x", "slug": "x", "title": "x", "body": "x", "topic": "rol", "status": "draft", "is_placeholder": False, "user_uid": "x", "published_at": None, "created_at": now_iso(), "updated_at": now_iso(), "deleted_at": None, "deleted_by": None, }, "join_requests": { "uid": "x", "name": "x", "contact": "x", "repo_url": None, "message": "", "status": "open", "ip_hash": None, "link_title": None, "link_checked_at": None, "link_status": None, "created_at": now_iso(), "updated_at": now_iso(), "deleted_at": None, "deleted_by": None, }, "media_items": { "uid": "x", "filename": "x.jpg", "kind": "flyer", "caption": "", "position": 0, "width": 0, "height": 0, "phash": None, "thumb_directory": None, "thumb_name": None, "created_at": now_iso(), "updated_at": now_iso(), "deleted_at": None, "deleted_by": None, }, "notifications": { "uid": "x", "user_uid": "x", "kind": "system.maintenance", "message": "x", "url": "/", "read_at": None, "created_at": now_iso(), "deleted_at": None, "deleted_by": None, }, "site_settings": {"uid": "setting_x", "key": "x", "value": "x", "updated_at": now_iso()}, "cache_state": {"name": "settings", "version": 0, "updated_at": now_iso()}, "service_state": { "name": "backup", "status": "stopped", "last_run": None, "next_run": None, "started_at": None, "heartbeat": None, "logs": "[]", "metrics": "{}", "updated_at": now_iso(), }, "audit_log": { "uid": "x", "event_key": "auth.login", "category": "auth", "actor_kind": "user", "actor_uid": None, "origin": "web", "result": "ok", "ip": None, "user_agent": None, "message": None, "payload": "{}", "created_at": now_iso(), }, "audit_links": {"audit_uid": "x", "target_type": "user", "target_uid": "x"}, "visit_stats": {"day": "2026-01-01", "path": "/", "status": 200, "hits": 0}, "backups": { "uid": "x", "target": "database", "status": "pending", "requested_by": "x", "directory": None, "stored_name": None, "size_bytes": None, "error": None, "created_at": now_iso(), "finished_at": None, }, } def _ensure_table(name, example): table = db.get_table(name) for column, value in example.items(): table.create_column_by_example(column, value) return table def _index(sql): try: with db: db.query(sql) except Exception as exc: logger.warning("index ensure failed: %s (%s)", sql, exc) def _seed_settings(): if "site_settings" not in db.tables: return for key, value in SETTINGS_DEFAULTS.items(): if db["site_settings"].find_one(key=key) is None: db["site_settings"].insert({"uid": f"default_{key}", "key": key, "value": value, "updated_at": now_iso()}) def _seed_notification_key(): path = config.NOTIFICATION_KEY_FILE if not path.exists(): path.write_text(generate_uid(), encoding="utf-8") path.chmod(0o600) def _backfill_api_keys(): if "users" not in db.tables: return with db: db.query("UPDATE users SET api_key = :key WHERE api_key IS NULL OR api_key = ''", key=generate_uid()) def init_db(): for name, example in TABLE_EXAMPLES.items(): _ensure_table(name, example) _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_uid ON users(uid)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users(username)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_api_key ON users(api_key)") _index("CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at)") _index("CREATE INDEX IF NOT EXISTS idx_users_trash ON users(deleted_at) WHERE deleted_at IS NOT NULL") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_uid ON sessions(uid)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)") _index("CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_uid, created_at)") _index("CREATE INDEX IF NOT EXISTS idx_sessions_trash ON sessions(deleted_at) WHERE deleted_at IS NOT NULL") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_posts_uid ON posts(uid)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_posts_slug ON posts(slug)") _index( "CREATE INDEX IF NOT EXISTS idx_posts_published ON posts(topic, published_at) " "WHERE deleted_at IS NULL AND status = 'published'" ) _index("CREATE INDEX IF NOT EXISTS idx_posts_author ON posts(user_uid, created_at)") _index("CREATE INDEX IF NOT EXISTS idx_posts_trash ON posts(deleted_at) WHERE deleted_at IS NOT NULL") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_join_requests_uid ON join_requests(uid)") _index("CREATE INDEX IF NOT EXISTS idx_join_requests_status ON join_requests(status, created_at)") _index("CREATE INDEX IF NOT EXISTS idx_join_requests_trash ON join_requests(deleted_at) WHERE deleted_at IS NOT NULL") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_media_items_uid ON media_items(uid)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_media_items_filename ON media_items(filename)") _index("CREATE INDEX IF NOT EXISTS idx_media_items_kind ON media_items(kind, position)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_notifications_uid ON notifications(uid)") _index("CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_uid, created_at)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_site_settings_uid ON site_settings(uid)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_site_settings_key ON site_settings(key)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_cache_state_name ON cache_state(name)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_service_state_name ON service_state(name)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_log_uid ON audit_log(uid)") _index("CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at)") _index("CREATE INDEX IF NOT EXISTS idx_audit_links_audit ON audit_links(audit_uid)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_visit_stats_day_path ON visit_stats(day, path, status)") _index("CREATE UNIQUE INDEX IF NOT EXISTS idx_backups_uid ON backups(uid)") _seed_settings() _seed_notification_key() _backfill_api_keys() with db: has_stats = next(iter(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_stat1'")), None) if has_stats is None: db.query("ANALYZE") db.query("PRAGMA optimize")