Molodetz blogrol volgens DPP-template

This commit is contained in:
2026-10-05 09:36:20 +02:00
commit f290a4c766
277 changed files with 15758 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
# retoor <retoor@molodetz.nl>
import argparse
import sys
from molodetz.cli import commands
def build_parser():
parser = argparse.ArgumentParser(prog="molodetz", description="Beheer-CLI voor Molodetz.")
groups = parser.add_subparsers(dest="group", required=True)
role = groups.add_parser("role", help="Rollen lezen en zetten").add_subparsers(dest="action", required=True)
role_get = role.add_parser("get")
role_get.add_argument("username")
role_get.set_defaults(handler=commands.role_get)
role_set = role.add_parser("set")
role_set.add_argument("username")
role_set.add_argument("role", choices=["Member", "Admin"])
role_set.set_defaults(handler=commands.role_set)
apikey = groups.add_parser("apikey", help="API-sleutels").add_subparsers(dest="action", required=True)
key_get = apikey.add_parser("get")
key_get.add_argument("username")
key_get.set_defaults(handler=commands.apikey_get)
key_reset = apikey.add_parser("reset")
key_reset.add_argument("username")
key_reset.set_defaults(handler=commands.apikey_reset)
apikey.add_parser("backfill").set_defaults(handler=commands.apikey_backfill)
posts = groups.add_parser("posts", help="Berichten").add_subparsers(dest="action", required=True)
posts.add_parser("list").set_defaults(handler=commands.posts_list)
posts_import = posts.add_parser("import", help="Importeer .md of .pdf als concept")
posts_import.add_argument("path")
posts_import.add_argument("--topic", default="rol", choices=["rol", "standaard"])
posts_import.add_argument("--title")
posts_import.add_argument("--publish", action="store_true")
posts_import.set_defaults(handler=commands.posts_import)
joins = groups.add_parser("joins", help="Aanmeldingen").add_subparsers(dest="action", required=True)
joins.add_parser("list").set_defaults(handler=commands.joins_list)
backups = groups.add_parser("backups", help="Back-ups").add_subparsers(dest="action", required=True)
backups.add_parser("list").set_defaults(handler=commands.backups_list)
backup_run = backups.add_parser("run")
backup_run.add_argument("--target", default="database", choices=["database", "uploads", "keys", "full"])
backup_run.add_argument("--codec", default="zstd", choices=["zstd", "brotli"])
backup_run.set_defaults(handler=commands.backups_run)
backup_prune = backups.add_parser("prune")
backup_prune.add_argument("--keep", type=int, default=14)
backup_prune.add_argument("--dry-run", action="store_true")
backup_prune.set_defaults(handler=commands.backups_prune)
backup_clear = backups.add_parser("clear")
backup_clear.add_argument("--dry-run", action="store_true")
backup_clear.set_defaults(handler=commands.backups_clear)
sessions = groups.add_parser("sessions", help="Sessies").add_subparsers(dest="action", required=True)
sessions_prune = sessions.add_parser("prune")
sessions_prune.add_argument("--dry-run", action="store_true")
sessions_prune.set_defaults(handler=commands.sessions_prune)
audit = groups.add_parser("audit", help="Auditlog").add_subparsers(dest="action", required=True)
audit_prune = audit.add_parser("prune")
audit_prune.add_argument("--days", type=int, default=365)
audit_prune.add_argument("--dry-run", action="store_true")
audit_prune.set_defaults(handler=commands.audit_prune)
system = groups.add_parser("system", help="Systeemonderhoud").add_subparsers(dest="action", required=True)
system_prune = system.add_parser("prune", help="Verwijder wees-blobs")
system_prune.add_argument("--dry-run", action="store_true")
system_prune.set_defaults(handler=commands.system_prune)
system.add_parser("sql-lint", help="Parse alle SQL-literals met sqlglot").set_defaults(handler=commands.system_sql_lint)
emoji = groups.add_parser("emoji", help="Emoji-kaart").add_subparsers(dest="action", required=True)
emoji.add_parser("regenerate").set_defaults(handler=commands.emoji_regenerate)
return parser
def main(argv=None):
parser = build_parser()
args = parser.parse_args(argv)
commands.prepare()
return args.handler(args) or 0
if __name__ == "__main__":
sys.exit(main())
+6
View File
@@ -0,0 +1,6 @@
# retoor <retoor@molodetz.nl>
import sys
from molodetz.cli import main
sys.exit(main())
+154
View File
@@ -0,0 +1,154 @@
# retoor <retoor@molodetz.nl>
from datetime import datetime, timedelta, timezone
from pathlib import Path
from pypdf import PdfReader
from molodetz import config
from molodetz.attachments import orphan_blobs
from molodetz.bootstrap import bootstrap_admin
from molodetz.content import create_post
from molodetz.database import (
all_posts,
enqueue_backup,
generate_uid,
get_user_by_username,
init_db,
list_backups,
list_join_requests,
list_users,
prune_audit,
prune_expired_sessions,
set_role,
update_user,
)
from molodetz.database.sql_lint import lint
from molodetz.emoji_builder import write_js_module
from molodetz.services.backup import clear_backups, process_backup, prune_backups
from molodetz.utils.audit import record_system
def prepare():
config.ensure_data_dirs()
init_db()
def _user(username):
user = get_user_by_username(username)
if user is None:
raise SystemExit(f"Onbekende gebruiker: {username}")
return user
def role_get(args):
print(_user(args.username).get("role"))
def role_set(args):
user = _user(args.username)
set_role(user["uid"], args.role)
record_system("admin.user.role", actor_kind="cli", origin="cli", payload={"role": args.role}, targets=[("user", user["uid"])])
print(f"{user['username']} -> {args.role}")
def apikey_get(args):
print(_user(args.username).get("api_key"))
def apikey_reset(args):
user = _user(args.username)
key = generate_uid()
update_user(user["uid"], api_key=key)
record_system("profile.apikey.regenerate", actor_kind="cli", origin="cli", targets=[("user", user["uid"])])
print(key)
def apikey_backfill(args):
changed = 0
for user in list_users(limit=100000):
if not user.get("api_key"):
update_user(user["uid"], api_key=generate_uid())
changed += 1
print(f"{changed} sleutels aangevuld")
def posts_list(args):
for post in all_posts():
flag = " [placeholder]" if post.get("is_placeholder") else ""
print(f"{post['uid']} {post['status']:<9} {post['topic']:<9} {post['title']}{flag}")
def read_source(path):
path = Path(path)
if path.suffix.lower() == ".pdf":
reader = PdfReader(str(path))
return "\n\n".join((page.extract_text() or "").strip() for page in reader.pages).strip()
return path.read_text(encoding="utf-8")
def posts_import(args):
path = Path(args.path)
if not path.is_file():
raise SystemExit(f"Bestand niet gevonden: {path}")
body = read_source(path)
title = args.title or path.stem.replace("-", " ").replace("_", " ").strip().capitalize()
author = bootstrap_admin() or _user(config.ADMIN_USERNAME)
post = create_post(author, title, body, args.topic, "published" if args.publish else "draft")
print(f"Geimporteerd: {post['uid']} {post['title']}")
def joins_list(args):
for row in list_join_requests(limit=1000):
print(f"{row['created_at']} {row['status']:<9} {row['name']} {row['contact']} {row.get('repo_url') or '-'}")
def backups_list(args):
for row in list_backups(limit=1000):
print(f"{row['uid']} {row['status']:<8} {row['target']:<9} {row.get('stored_name') or '-'} {row.get('size_bytes') or 0}")
def backups_run(args):
row = enqueue_backup(args.target, None)
ok = process_backup(row, args.codec)
print("klaar" if ok else "mislukt")
return 0 if ok else 1
def backups_prune(args):
count = prune_backups(args.keep, dry_run=args.dry_run)
print(f"{count} archieven {'zouden worden verwijderd' if args.dry_run else 'verwijderd'}")
def backups_clear(args):
count = clear_backups(dry_run=args.dry_run)
print(f"{count} archieven {'zouden worden verwijderd' if args.dry_run else 'verwijderd'}")
def sessions_prune(args):
print(f"{prune_expired_sessions(dry_run=args.dry_run)} verlopen sessies")
def audit_prune(args):
cutoff = (datetime.now(timezone.utc) - timedelta(days=args.days)).isoformat()
print(f"{prune_audit(cutoff, dry_run=args.dry_run)} auditregels ouder dan {args.days} dagen")
def system_prune(args):
orphans = orphan_blobs()
for path in orphans:
print(("zou verwijderen: " if args.dry_run else "verwijderd: ") + str(path))
if not args.dry_run:
path.unlink(missing_ok=True)
print(f"{len(orphans)} wees-blobs")
def system_sql_lint(args):
problems = lint()
for problem in problems:
print(problem)
print(f"{len(problems)} problemen")
return 1 if problems else 0
def emoji_regenerate(args):
print(write_js_module())