feat: integrate Just Works app and native business setup

Preserve hosted website UX inside Archipelago, add isolated Business and owner-authorized native wallet setup, and include deployment handoff. Based on main with native signer import already accepted.
This commit is contained in:
yaya
2026-10-08 19:39:38 +01:00
parent 8d17597202
commit 94899e69d3
152 changed files with 22873 additions and 2 deletions
@@ -0,0 +1,111 @@
"""Small public signage projection. No staff data, customer text or payment proofs."""
import json
import threading
import time
from collections import OrderedDict
from datetime import datetime, timezone
TTL = 120
_contexts = OrderedDict()
_lock = threading.Lock()
_workers = threading.BoundedSemaphore(2)
def timestamp(value):
try:
parsed = datetime.fromisoformat(str(value).replace('Z', '+00:00'))
if parsed.tzinfo is None:
return None
return parsed.timestamp()
except (ValueError, TypeError, OverflowError):
return None
def iso(value):
return datetime.fromtimestamp(value, timezone.utc).isoformat(timespec='seconds')
def configure(npub, nsite):
"""Only called with server-resolved Core metadata, never client-supplied labels."""
sections = nsite.get('page', {}).get('sections', [])
menu = next((s for s in sections if s.get('type') == 'menu' and s.get('enabled')), {})
catalog = {str(item.get('id') or f'item-{i}')[:40]: str(item.get('title') or item.get('name') or 'Item')[:80]
for i, item in enumerate(menu.get('items', [])[:50])}
identity = nsite.get('identity', {})
lud16 = str(identity.get('profile', {}).get('lud16') or identity.get('npubCashAddress') or f'{npub}@npub.cash').strip()
with _lock:
context = _contexts.setdefault(npub, {'tips': [], 'checked': 0, 'busy': False, 'available': False})
if context.get('lud16') != lud16:
context.update(tips=[], checked=0, available=False)
context.update(catalog=catalog, lud16=lud16)
_contexts.move_to_end(npub)
while len(_contexts) > 64:
_contexts.popitem(last=False)
def tips(npub, load, current):
"""Network work stays off the polling path; bounded concurrency/cache, fail closed."""
with _lock:
context = _contexts.get(npub)
if not context:
return [], False
if not context['busy'] and time.monotonic() - context['checked'] >= 15 and _workers.acquire(blocking=False):
context['busy'] = True
lud16 = context['lud16']
def refresh():
try:
result = load(npub, lud16)
with _lock:
if _contexts.get(npub) is context and context['lud16'] == lud16:
context.update(tips=result[:30], available=True)
except Exception:
with _lock:
context.update(tips=[], available=False)
finally:
with _lock:
context.update(checked=time.monotonic(), busy=False)
_workers.release()
threading.Thread(target=refresh, daemon=True).start()
return list(context['tips']), context['available']
def projection(conn, npub, load_tips, current=None):
current = time.time() if current is None else current
cutoff = iso(current - TTL)
events = []
def event(kind, stamp, **fields):
occurred = timestamp(stamp)
if occurred is not None and current - TTL < occurred <= current:
events.append({'type': kind, 'occurred_at': iso(occurred), 'expires_at': iso(occurred + TTL), **fields})
with _lock:
catalog = dict(_contexts.get(npub, {}).get('catalog', {}))
catalog.update({row['id']: row['name'][:80] for row in conn.execute(
'SELECT id,name FROM products WHERE merchant_id=? AND enabled=1', (npub,))})
for row in conn.execute("SELECT table_number,items_json,status,created_at FROM orders WHERE merchant_id=? AND status IN ('new','accepted') AND created_at>? ORDER BY created_at DESC LIMIT 30", (npub, cutoff)):
try:
raw = json.loads(row['items_json'])
raw = raw if isinstance(raw, list) else []
except (ValueError, TypeError):
raw = []
items = []
for item in raw[:30]:
if not isinstance(item, dict) or not isinstance(item.get('id'), str) or item['id'] == 'request' or item['id'] not in catalog:
continue
qty = item.get('qty')
if isinstance(qty, int) and not isinstance(qty, bool) and 0 < qty <= 20:
items.append({'name': catalog[item['id']], 'qty': qty})
event('order', row['created_at'], table=row['table_number'], items=items[:4], status=row['status'])
for row in conn.execute("SELECT amount_cents,amount_sats,settled_at FROM quick_payments WHERE merchant_id=? AND status='paid' AND settled_at>? ORDER BY settled_at DESC LIMIT 30", (npub, cutoff)):
event('justpay', row['settled_at'], amount_cents=row['amount_cents'], amount_sats=row['amount_sats'])
recent_tips, available = tips(npub, load_tips, current)
for tip in recent_tips:
# Explicit allowlist even for our own helper; no receipt ids or payer identities.
sats = tip.get('sats')
if isinstance(sats, int) and not isinstance(sats, bool) and sats > 0:
event('tip', tip.get('occurred_at'), amount_sats=sats)
events.sort(key=lambda item: item['occurred_at'], reverse=True)
return {'events': events[:30], 'server_time': iso(current), 'ttl_seconds': TTL, 'tips_available': available}