Files
ad/molodetz/database/init_db.py
T

234 lines
7.9 KiB
Python

# retoor <retoor@molodetz.nl>
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():
uid = f"default_{key}"
if db["site_settings"].find_one(uid=uid) is None:
db["site_settings"].insert({"uid": uid, "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")