Initial deploy setup
deploy / deploy (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-21 14:00:00 +02:00
commit b8091e59bd
285 changed files with 27854 additions and 0 deletions
@@ -0,0 +1,85 @@
from __future__ import annotations
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError
from apps.sources.services.discovery import (
discover_from_email,
discover_from_feed,
discover_from_html,
discover_from_sitemap,
persist_discovery_candidates,
)
class Command(BaseCommand):
help = (
"Voert bronontdekking uit op lokaal aangeleverde content "
"en slaat alleen kandidaatregels op met policy review."
)
def add_arguments(self, parser):
parser.add_argument(
"mode",
choices=["html", "sitemap", "feed", "email"],
help="Bronmodus voor ontdekking",
)
parser.add_argument(
"--input",
required=True,
dest="input_path",
help="Pad naar inputbestand",
)
parser.add_argument(
"--base-url",
dest="base_url",
help="Base URL voor relativiteitsresolutie",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Geen database-write; toon alleen ontdekte kandidaten",
)
def handle(self, *args, **options):
mode = options["mode"]
input_path = Path(options["input_path"]).resolve()
base_url = (options["base_url"] or "").strip()
if mode in {"html", "sitemap", "feed"} and not base_url:
raise CommandError("--base-url is verplicht voor html/sitemap/feed")
if not input_path.is_file():
raise CommandError(f"Inputbestand niet gevonden: {input_path}")
if mode in {"html", "sitemap", "feed"}:
content = input_path.read_text(encoding="utf-8")
else:
content = input_path.read_bytes()
if mode == "html":
candidates = discover_from_html(content, base_url=base_url)
elif mode == "sitemap":
candidates = discover_from_sitemap(content, base_url=base_url)
elif mode == "feed":
candidates = discover_from_feed(content, base_url=base_url)
else:
candidates = discover_from_email(content)
self.stdout.write(f"Ontdekt {len(candidates)} kandidaten (mode={mode}).")
if not candidates:
self.stdout.write(self.style.WARNING("Geen bruikbare kandidaten gevonden."))
return
if options["dry_run"]:
self.stdout.write(self.style.WARNING("Dry-run modus: er wordt niet geschreven."))
for candidate in candidates:
self.stdout.write(f"- {candidate.source_type} {candidate.url} [{candidate.reason}]")
return
created, updated, skipped = persist_discovery_candidates(candidates)
self.stdout.write(
self.style.SUCCESS(
f"Persist result: {created} nieuw, {updated} bijgewerkt, {skipped} ongewijzigd."
)
)
@@ -0,0 +1,68 @@
from __future__ import annotations
import hashlib
from datetime import timedelta
from pathlib import Path
from urllib.parse import urlsplit
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from apps.jobs.services.pipeline import process_raw_document
from apps.sources.models import RawDocument, Source, SourceRun
class Command(BaseCommand):
help = "Importeert een lokale HTML/XML-fixture via dezelfde pipeline als een live bron."
def add_arguments(self, parser):
parser.add_argument("path")
parser.add_argument("--url", required=True)
parser.add_argument("--source-name", default="Lokale fixture")
parser.add_argument("--source-type", default=Source.Type.EMPLOYER)
def handle(self, *args, **options):
path = Path(options["path"]).resolve()
if not path.is_file():
raise CommandError(f"Bestand bestaat niet: {path}")
content = path.read_text(encoding="utf-8")
hostname = (urlsplit(options["url"]).hostname or "").lower()
if not hostname:
raise CommandError("--url bevat geen geldig domein")
source, _ = Source.objects.get_or_create(
domain=hostname,
source_type=options["source_type"],
defaults={
"name": options["source_name"],
"base_url": options["url"],
"status": Source.Status.ACTIVE,
"policy": Source.Policy.ALLOW,
"parser_key": "auto",
},
)
run = SourceRun.objects.create(source=source)
suffix = path.suffix.lower()
kind = RawDocument.Kind.XML if suffix in {".xml", ".rss"} else RawDocument.Kind.HTML
document = RawDocument.objects.create(
source=source,
source_run=run,
url=options["url"],
final_url=options["url"],
kind=kind,
content_type="application/xml" if kind == RawDocument.Kind.XML else "text/html",
content_hash=hashlib.sha256(content.encode()).hexdigest(),
body_text=content,
byte_length=len(content.encode()),
retain_until=timezone.now() + timedelta(days=settings.RAW_DOCUMENT_RETENTION_DAYS),
)
metrics = process_raw_document(document)
run.finish(
SourceRun.Status.SUCCESS,
extracted_count=int(metrics["extracted"]),
created_count=int(metrics["created"]),
updated_count=int(metrics["updated"]),
duplicate_count=int(metrics["duplicates"]),
metrics=metrics,
)
self.stdout.write(self.style.SUCCESS(str(metrics)))
@@ -0,0 +1,55 @@
from __future__ import annotations
from pathlib import Path
from urllib.parse import urlsplit
import yaml
from django.core.management.base import BaseCommand, CommandError
from apps.sources.models import Source
from apps.sources.services.policy import is_denied_domain
class Command(BaseCommand):
help = "Laadt gecontroleerde bronseeds uit YAML."
def add_arguments(self, parser):
parser.add_argument("path", nargs="?", default="config-data/seed_sources.yaml")
def handle(self, *args, **options):
path = Path(options["path"])
if not path.is_file():
raise CommandError(f"Seedbestand ontbreekt: {path}")
payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
created = updated = 0
for item in payload.get("sources", []):
url = str(item.get("url") or "").strip()
domain = (urlsplit(url).hostname or "").lower()
if not domain:
self.stderr.write(f"Overgeslagen zonder domein: {item}")
continue
deny = is_denied_domain(domain)
source, was_created = Source.objects.update_or_create(
domain=domain,
source_type=item.get("type", Source.Type.EMPLOYER),
defaults={
"name": item.get("name") or domain,
"base_url": url,
"status": Source.Status.PAUSED
if deny
else item.get("status", Source.Status.TRIAL),
"policy": Source.Policy.DENY
if deny
else item.get("policy", Source.Policy.REVIEW),
"policy_reason": "Standaard platformdenylist"
if deny
else item.get("policy_reason", ""),
"parser_key": item.get("parser", "auto"),
"crawl_interval_minutes": int(item.get("crawl_interval_minutes", 720)),
},
)
created += int(was_created)
updated += int(not was_created)
self.stdout.write(
self.style.SUCCESS(f"Bronnen: {created} aangemaakt, {updated} bijgewerkt")
)