forked from aiolabs/libra
Rename Castle Accounting extension to Libra
Full identifier rename: module path lnbits.extensions.castle →
lnbits.extensions.libra, DB ext_castle → ext_libra, URL prefix
/castle/ → /libra/, manifest id castle → libra, fava ledger slug
default castle-ledger → libra-ledger, Beancount source metadata
castle-api → libra-api and link prefixes castle-{entry,tx}- →
libra-{entry,tx}-, column castle_wallet_id → libra_wallet_id, all
Python/JS/HTML identifiers (castle_ext, CastleSettings,
castle_reference, castleWalletConfigured, etc.).
Display name "Castle Accounting" → "Libra" (the scales/balance
metaphor — fits double-entry bookkeeping).
No backward compat: production hosts will be force-updated. Old
castle-prefixed Beancount metadata in existing Fava ledgers is
historical; new entries use libra-* prefixes going forward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9c577c740c
commit
c174cda48d
44 changed files with 953 additions and 953 deletions
64
tasks.py
64
tasks.py
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Background tasks for Castle accounting extension.
|
||||
Background tasks for Libra accounting extension.
|
||||
These tasks handle automated reconciliation checks and maintenance.
|
||||
"""
|
||||
|
||||
|
|
@ -62,11 +62,11 @@ async def check_all_balance_assertions() -> dict:
|
|||
|
||||
# Log results
|
||||
if results["failed"] > 0:
|
||||
print(f"[CASTLE] Daily reconciliation check: {results['failed']} FAILED assertions!")
|
||||
print(f"[LIBRA] Daily reconciliation check: {results['failed']} FAILED assertions!")
|
||||
for failed in results["failed_assertions"]:
|
||||
print(f" - Account {failed['account_id']}: expected {failed['expected_sats']}, got {failed['actual_sats']}")
|
||||
else:
|
||||
print(f"[CASTLE] Daily reconciliation check: All {results['passed']} assertions passed ✓")
|
||||
print(f"[LIBRA] Daily reconciliation check: All {results['passed']} assertions passed ✓")
|
||||
|
||||
return results
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ async def scheduled_daily_reconciliation():
|
|||
This function is meant to be called by a scheduler (cron, systemd timer, etc.)
|
||||
or by LNbits background task system.
|
||||
"""
|
||||
print(f"[CASTLE] Running scheduled daily reconciliation check at {datetime.now()}")
|
||||
print(f"[LIBRA] Running scheduled daily reconciliation check at {datetime.now()}")
|
||||
|
||||
try:
|
||||
results = await check_all_balance_assertions()
|
||||
|
|
@ -86,38 +86,38 @@ async def scheduled_daily_reconciliation():
|
|||
# TODO: Send notifications if there are failures
|
||||
# This could send email, webhook, or in-app notification
|
||||
if results["failed"] > 0:
|
||||
print(f"[CASTLE] WARNING: {results['failed']} balance assertions failed!")
|
||||
print(f"[LIBRA] WARNING: {results['failed']} balance assertions failed!")
|
||||
# Future: Send alert notification
|
||||
|
||||
return results
|
||||
except Exception as e:
|
||||
print(f"[CASTLE] Error in scheduled reconciliation: {e}")
|
||||
print(f"[LIBRA] Error in scheduled reconciliation: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def scheduled_account_sync():
|
||||
"""
|
||||
Scheduled task that runs hourly to sync accounts from Beancount to Castle DB.
|
||||
Scheduled task that runs hourly to sync accounts from Beancount to Libra DB.
|
||||
|
||||
This ensures Castle DB stays in sync with Beancount (source of truth) by
|
||||
automatically adding any new accounts created in Beancount to Castle's
|
||||
This ensures Libra DB stays in sync with Beancount (source of truth) by
|
||||
automatically adding any new accounts created in Beancount to Libra's
|
||||
metadata database for permission tracking.
|
||||
"""
|
||||
from .account_sync import sync_accounts_from_beancount
|
||||
|
||||
logger.info(f"[CASTLE] Running scheduled account sync at {datetime.now()}")
|
||||
logger.info(f"[LIBRA] Running scheduled account sync at {datetime.now()}")
|
||||
|
||||
try:
|
||||
stats = await sync_accounts_from_beancount(force_full_sync=False)
|
||||
|
||||
if stats["accounts_added"] > 0:
|
||||
logger.info(
|
||||
f"[CASTLE] Account sync: Added {stats['accounts_added']} new accounts"
|
||||
f"[LIBRA] Account sync: Added {stats['accounts_added']} new accounts"
|
||||
)
|
||||
|
||||
if stats["errors"]:
|
||||
logger.warning(
|
||||
f"[CASTLE] Account sync: {len(stats['errors'])} errors encountered"
|
||||
f"[LIBRA] Account sync: {len(stats['errors'])} errors encountered"
|
||||
)
|
||||
for error in stats["errors"][:5]: # Log first 5 errors
|
||||
logger.error(f" - {error}")
|
||||
|
|
@ -125,24 +125,24 @@ async def scheduled_account_sync():
|
|||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[CASTLE] Error in scheduled account sync: {e}")
|
||||
logger.error(f"[LIBRA] Error in scheduled account sync: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def wait_for_account_sync():
|
||||
"""
|
||||
Background task that periodically syncs accounts from Beancount to Castle DB.
|
||||
Background task that periodically syncs accounts from Beancount to Libra DB.
|
||||
|
||||
Runs hourly to ensure Castle DB stays in sync with Beancount.
|
||||
Runs hourly to ensure Libra DB stays in sync with Beancount.
|
||||
"""
|
||||
logger.info("[CASTLE] Account sync background task started")
|
||||
logger.info("[LIBRA] Account sync background task started")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Run sync
|
||||
await scheduled_account_sync()
|
||||
except Exception as e:
|
||||
logger.error(f"[CASTLE] Account sync error: {e}")
|
||||
logger.error(f"[LIBRA] Account sync error: {e}")
|
||||
|
||||
# Wait 1 hour before next sync
|
||||
await asyncio.sleep(3600) # 3600 seconds = 1 hour
|
||||
|
|
@ -157,9 +157,9 @@ def start_daily_reconciliation_task():
|
|||
|
||||
For cron setup:
|
||||
# Run daily at 2 AM
|
||||
0 2 * * * curl -X POST http://localhost:5000/castle/api/v1/tasks/daily-reconciliation -H "X-Api-Key: YOUR_ADMIN_KEY"
|
||||
0 2 * * * curl -X POST http://localhost:5000/libra/api/v1/tasks/daily-reconciliation -H "X-Api-Key: YOUR_ADMIN_KEY"
|
||||
"""
|
||||
print("[CASTLE] Daily reconciliation task registered")
|
||||
print("[LIBRA] Daily reconciliation task registered")
|
||||
# In a production system, you would register this with LNbits task scheduler
|
||||
# For now, it can be triggered manually via API endpoint
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ async def wait_for_paid_invoices():
|
|||
before the payment is detected by client-side polling.
|
||||
"""
|
||||
invoice_queue = Queue()
|
||||
register_invoice_listener(invoice_queue, "ext_castle")
|
||||
register_invoice_listener(invoice_queue, "ext_libra")
|
||||
|
||||
while True:
|
||||
payment = await invoice_queue.get()
|
||||
|
|
@ -182,10 +182,10 @@ async def wait_for_paid_invoices():
|
|||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
"""
|
||||
Handle a paid Castle invoice by automatically submitting to Fava.
|
||||
Handle a paid Libra invoice by automatically submitting to Fava.
|
||||
|
||||
This function is called automatically when any invoice on the Castle wallet
|
||||
is paid. It checks if the invoice is a Castle payment and records it in
|
||||
This function is called automatically when any invoice on the Libra wallet
|
||||
is paid. It checks if the invoice is a Libra payment and records it in
|
||||
Beancount via Fava.
|
||||
|
||||
Concurrency Protection:
|
||||
|
|
@ -194,13 +194,13 @@ async def on_invoice_paid(payment: Payment) -> None:
|
|||
- Uses idempotent entry creation to prevent duplicate entries even if
|
||||
the same payment is processed multiple times
|
||||
"""
|
||||
# Only process Castle-specific payments
|
||||
if not payment.extra or payment.extra.get("tag") != "castle":
|
||||
# Only process Libra-specific payments
|
||||
if not payment.extra or payment.extra.get("tag") != "libra":
|
||||
return
|
||||
|
||||
user_id = payment.extra.get("user_id")
|
||||
if not user_id:
|
||||
logger.warning(f"Castle invoice {payment.payment_hash} missing user_id in metadata")
|
||||
logger.warning(f"Libra invoice {payment.payment_hash} missing user_id in metadata")
|
||||
return
|
||||
|
||||
from .fava_client import get_fava_client
|
||||
|
|
@ -216,7 +216,7 @@ async def on_invoice_paid(payment: Payment) -> None:
|
|||
user_lock = fava.get_user_lock(user_id)
|
||||
|
||||
async with user_lock:
|
||||
logger.info(f"Recording Castle payment {payment.payment_hash} for user {user_id[:8]} to Fava")
|
||||
logger.info(f"Recording Libra payment {payment.payment_hash} for user {user_id[:8]} to Fava")
|
||||
|
||||
try:
|
||||
from decimal import Decimal
|
||||
|
|
@ -246,14 +246,14 @@ async def on_invoice_paid(payment: Payment) -> None:
|
|||
total_fiat_balance = fiat_balances.get(fiat_currency, Decimal(0))
|
||||
|
||||
# Determine receivables and payables based on balance
|
||||
# Positive balance = user owes castle (receivable)
|
||||
# Negative balance = castle owes user (payable)
|
||||
# Positive balance = user owes libra (receivable)
|
||||
# Negative balance = libra owes user (payable)
|
||||
if total_fiat_balance > 0:
|
||||
# User owes castle
|
||||
# User owes libra
|
||||
total_receivable = total_fiat_balance
|
||||
total_payable = Decimal(0)
|
||||
else:
|
||||
# Castle owes user
|
||||
# Libra owes user
|
||||
total_receivable = Decimal(0)
|
||||
total_payable = abs(total_fiat_balance)
|
||||
|
||||
|
|
@ -318,5 +318,5 @@ async def on_invoice_paid(payment: Payment) -> None:
|
|||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error recording Castle payment {payment.payment_hash}: {e}")
|
||||
logger.error(f"Error recording Libra payment {payment.payment_hash}: {e}")
|
||||
raise
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue