# -*- coding: utf-8 -*- """ AzA To-Do Server – Mobile PWA mit Supabase Cloud-Sync ====================================================== Die App synchronisiert sich direkt mit Supabase (kostenlose Cloud-DB). Das iPhone braucht KEINEN laufenden PC – funktioniert von ueberall. Der lokale Server wird NUR einmalig benoetigt, um die App per QR-Code auf das iPhone zu laden. Danach laeuft die App eigenstaendig. Nutzung: py todo_server.py Einmalig starten, QR scannen, fertig. """ import os import sys import json import socket import subprocess import threading import time import webbrowser import base64 from datetime import datetime from http.server import HTTPServer, BaseHTTPRequestHandler from aza_tls import check_tls_or_exit, create_ssl_context, has_tls_config # ─── Konfiguration ─── BASE_DIR = os.path.dirname(os.path.abspath(__file__)) TODO_FILE = os.path.join(BASE_DIR, "kg_diktat_todos.json") PORT = 5111 _APP_VERSION = "1.0.0" _START_TIME = time.time() SUPABASE_URL = "https://cqbqkejdlxesxjdtkpfr.supabase.co" SUPABASE_ANON_KEY = "sb_publishable_r8YMw01FF-fYD9vHLnz7FQ_d8mhpVVQ" HAS_QRCODE = False try: import qrcode import io as _io HAS_QRCODE = True except ImportError: pass # ─── Logo ─── LOGO_PNG_FILE = os.path.join(BASE_DIR, "assets", "logo.png") LOGO_PNG_B64 = ( "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" ) def _get_logo_bytes(): if os.path.isfile(LOGO_PNG_FILE): with open(LOGO_PNG_FILE, "rb") as f: return f.read() return base64.b64decode(LOGO_PNG_B64) def _load_todos(): try: if os.path.isfile(TODO_FILE): with open(TODO_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: pass return [] def _save_todos(todos): try: with open(TODO_FILE, "w", encoding="utf-8") as f: json.dump(todos, f, indent=2, ensure_ascii=False) except Exception as e: print(f"Save error: {e}") def _supabase_push(todos=None): """Pusht Todos nach Supabase (Server-seitig).""" import urllib.request if todos is None: todos = _load_todos() payload = json.dumps({"data": todos}).encode("utf-8") req = urllib.request.Request( f"{SUPABASE_URL}/rest/v1/todo_sync?id=eq.1", data=payload, method="PATCH", headers={ "apikey": SUPABASE_ANON_KEY, "Authorization": f"Bearer {SUPABASE_ANON_KEY}", "Content-Type": "application/json", } ) try: urllib.request.urlopen(req, timeout=10) return True except Exception: return False def _get_local_ip(): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(2) s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] s.close() return ip except Exception: return "127.0.0.1" def _add_firewall_rule(): if sys.platform != "win32": return try: subprocess.run( ["netsh", "advfirewall", "firewall", "add", "rule", "name=AzA Todo Server (Port)", "dir=in", "action=allow", "protocol=TCP", f"localport={PORT}"], capture_output=True, timeout=5 ) except Exception: pass # ─── HTTP-Server (nur fuer Ersteinrichtung + lokale Nutzung) ─── from socketserver import ThreadingMixIn class TodoServer(ThreadingMixIn, HTTPServer): allow_reuse_address = True daemon_threads = True class TodoHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass def _send_json(self, data, status=200): body = json.dumps(data, ensure_ascii=False).encode("utf-8") self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def _send_html(self, html, status=200): body = html.encode("utf-8") self.send_response(status) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def _send_text(self, text, content_type="text/plain", status=200): body = text.encode("utf-8") self.send_response(status) self.send_header("Content-Type", f"{content_type}; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def _send_bytes(self, data, content_type, status=200): self.send_response(status) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(data))) self.send_header("Cache-Control", "public, max-age=31536000") self.end_headers() self.wfile.write(data) def do_OPTIONS(self): self.send_response(200) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.end_headers() def do_GET(self): path = self.path.split("?")[0] if path == "/health": self._send_json({ "status": "ok", "version": _APP_VERSION, "uptime_s": int(time.time() - _START_TIME), "tls": has_tls_config(), }) elif path == "/": self._send_html(_build_pwa_html()) elif path == "/api/todos": self._send_json(_load_todos()) elif path == "/api/qr": self._handle_qr() elif path == "/manifest.json": self._send_json({ "name": "AzA To-Do", "short_name": "AzA To-Do", "start_url": "/", "display": "standalone", "background_color": "#E8F4FA", "theme_color": "#1a4d6d", "icons": [{"src": "/icon-180.png", "sizes": "180x180", "type": "image/png"}] }) elif path in ("/icon-180.png", "/apple-touch-icon.png", "/apple-touch-icon-precomposed.png"): self._send_bytes(_get_logo_bytes(), "image/png") elif path == "/sw.js": self._send_text(_build_sw_js(), "application/javascript") else: self.send_error(404) def do_POST(self): pass def do_PUT(self): pass def do_DELETE(self): pass def _handle_qr(self): if not HAS_QRCODE: self._send_json({"error": "qrcode nicht installiert"}, 500) return scheme = "https" if has_tls_config() else "http" url = f"{scheme}://{_get_local_ip()}:{PORT}" img = qrcode.make(url, box_size=8, border=2) buf = _io.BytesIO() img.save(buf, format="PNG") b64 = base64.b64encode(buf.getvalue()).decode() self._send_json({"qr": f"data:image/png;base64,{b64}", "url": url}) # ─── Service Worker ─── def _build_sw_js(): return ''' var CACHE_NAME = 'aza-todo-v5'; var URLS_TO_CACHE = ['/', '/manifest.json', '/icon-180.png']; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { return cache.addAll(URLS_TO_CACHE); }) ); self.skipWaiting(); }); self.addEventListener('activate', function(event) { event.waitUntil( caches.keys().then(function(names) { return Promise.all( names.filter(function(n) { return n !== CACHE_NAME; }) .map(function(n) { return caches.delete(n); }) ); }) ); self.clients.claim(); }); self.addEventListener('fetch', function(event) { var url = event.request.url; if (url.indexOf('supabase.co') !== -1) { event.respondWith(fetch(event.request)); return; } event.respondWith( caches.match(event.request).then(function(resp) { return resp || fetch(event.request).then(function(netResp) { return caches.open(CACHE_NAME).then(function(cache) { cache.put(event.request, netResp.clone()); return netResp; }); }).catch(function() { return new Response('