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:
Padreug 2026-05-05 10:24:46 +02:00
commit c174cda48d
44 changed files with 953 additions and 953 deletions

View file

@ -8,9 +8,9 @@
## Summary
Implemented two major improvements for Castle administration:
Implemented two major improvements for Libra administration:
1. **Account Synchronization** - Automatically sync accounts from Beancount → Castle DB
1. **Account Synchronization** - Automatically sync accounts from Beancount → Libra DB
2. **Bulk Permission Management** - Tools for managing permissions at scale
**Total Implementation Time**: ~4 hours
@ -23,24 +23,24 @@ Implemented two major improvements for Castle administration:
### Problem Solved
**Before**: Accounts existed in both Beancount and Castle DB, with manual sync required.
**After**: Automatic sync keeps Castle DB in sync with Beancount (source of truth).
**Before**: Accounts existed in both Beancount and Libra DB, with manual sync required.
**After**: Automatic sync keeps Libra DB in sync with Beancount (source of truth).
### Implementation
**New Module**: `castle/account_sync.py`
**New Module**: `libra/account_sync.py`
**Core Functions**:
```python
# 1. Full sync from Beancount to Castle
# 1. Full sync from Beancount to Libra
stats = await sync_accounts_from_beancount(force_full_sync=False)
# 2. Sync single account
success = await sync_single_account_from_beancount("Expenses:Food")
# 3. Ensure account exists (recommended before granting permissions)
exists = await ensure_account_exists_in_castle("Expenses:Marketing")
exists = await ensure_account_exists_in_libra("Expenses:Marketing")
# 4. Scheduled background sync (run hourly)
stats = await scheduled_account_sync()
@ -77,7 +77,7 @@ stats = await scheduled_account_sync()
```python
# Sync all accounts from Beancount
from castle.account_sync import sync_accounts_from_beancount
from libra.account_sync import sync_accounts_from_beancount
stats = await sync_accounts_from_beancount()
@ -96,11 +96,11 @@ Errors: 0
#### Before Granting Permission (Best Practice)
```python
from castle.account_sync import ensure_account_exists_in_castle
from castle.crud import create_account_permission
from libra.account_sync import ensure_account_exists_in_libra
from libra.crud import create_account_permission
# Ensure account exists in Castle DB first
account_exists = await ensure_account_exists_in_castle("Expenses:Marketing")
# Ensure account exists in Libra DB first
account_exists = await ensure_account_exists_in_libra("Expenses:Marketing")
if account_exists:
# Now safe to grant permission
@ -116,9 +116,9 @@ if account_exists:
```python
# Add to your scheduler (cron, APScheduler, etc.)
from castle.account_sync import scheduled_account_sync
from libra.account_sync import scheduled_account_sync
# Run every hour to keep Castle DB in sync
# Run every hour to keep Libra DB in sync
scheduler.add_job(
scheduled_account_sync,
'interval',
@ -142,7 +142,7 @@ Authorization: Bearer {admin_key}
```json
{
"total_beancount_accounts": 150,
"total_castle_accounts": 150,
"total_libra_accounts": 150,
"accounts_added": 2,
"accounts_updated": 0,
"accounts_skipped": 148,
@ -152,8 +152,8 @@ Authorization: Bearer {admin_key}
### Benefits
1. **Beancount as Source of Truth**: Castle DB automatically reflects Beancount state
2. **Reduced Manual Work**: No more manual account creation in Castle
1. **Beancount as Source of Truth**: Libra DB automatically reflects Beancount state
2. **Reduced Manual Work**: No more manual account creation in Libra
3. **Prevents Permission Errors**: Cannot grant permission on non-existent account
4. **Audit Trail**: Tracks which accounts were synced and when
5. **Safe Operations**: Continues on errors, never deletes accounts
@ -169,7 +169,7 @@ Authorization: Bearer {admin_key}
### Implementation
**New Module**: `castle/permission_management.py`
**New Module**: `libra/permission_management.py`
**Core Functions**:
@ -471,19 +471,19 @@ print(f"Permission types removed: {result['permission_types_removed']}")
# OLD: Manual permission creation (risky)
await create_account_permission(
user_id="alice",
account_id="acc123", # What if account doesn't exist in Castle DB?
account_id="acc123", # What if account doesn't exist in Libra DB?
permission_type=PermissionType.SUBMIT_EXPENSE,
granted_by="admin"
)
# NEW: Safe permission creation with account sync
from castle.account_sync import ensure_account_exists_in_castle
from libra.account_sync import ensure_account_exists_in_libra
# Ensure account exists first
account_exists = await ensure_account_exists_in_castle("Expenses:Marketing")
account_exists = await ensure_account_exists_in_libra("Expenses:Marketing")
if account_exists:
# Now safe - account guaranteed to be in Castle DB
# Now safe - account guaranteed to be in Libra DB
await create_account_permission(
user_id="alice",
account_id=account_id,
@ -497,10 +497,10 @@ else:
### Scheduler Integration
```python
# Add to your Castle extension startup
# Add to your Libra extension startup
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from castle.account_sync import scheduled_account_sync
from castle.permission_management import cleanup_expired_permissions
from libra.account_sync import scheduled_account_sync
from libra.permission_management import cleanup_expired_permissions
scheduler = AsyncIOScheduler()
@ -610,7 +610,7 @@ async def test_copy_permissions():
async def test_onboarding_workflow():
"""Test complete onboarding workflow"""
# 1. Sync account
await ensure_account_exists_in_castle("Expenses:Food")
await ensure_account_exists_in_libra("Expenses:Food")
# 2. Copy permissions from template user
result = await copy_permissions(
@ -745,19 +745,19 @@ logger.error(f"Account sync error: {error}")
## Migration Guide
### For Existing Castle Installations
### For Existing Libra Installations
**Step 1: Deploy New Modules**
```bash
# Copy new files to Castle extension
cp account_sync.py /path/to/castle/
cp permission_management.py /path/to/castle/
# Copy new files to Libra extension
cp account_sync.py /path/to/libra/
cp permission_management.py /path/to/libra/
```
**Step 2: Initial Account Sync**
```python
# Run once to sync existing accounts
from castle.account_sync import sync_accounts_from_beancount
from libra.account_sync import sync_accounts_from_beancount
stats = await sync_accounts_from_beancount(force_full_sync=True)
print(f"Synced {stats['accounts_added']} accounts")
@ -784,14 +784,14 @@ await bulk_grant_permission(...)
## Documentation Updates
**New files created**:
- ✅ `castle/account_sync.py` (230 lines)
- ✅ `castle/permission_management.py` (400 lines)
- ✅ `libra/account_sync.py` (230 lines)
- ✅ `libra/permission_management.py` (400 lines)
- ✅ `docs/PERMISSIONS-SYSTEM.md` (full permission system docs)
- ✅ `docs/ACCOUNT-SYNC-AND-PERMISSION-IMPROVEMENTS.md` (this file)
**Files to update**:
- `castle/views_api.py` - Add new admin endpoints
- `castle/README.md` - Document new features
- `libra/views_api.py` - Add new admin endpoints
- `libra/README.md` - Document new features
- `tests/` - Add comprehensive tests
---
@ -801,7 +801,7 @@ await bulk_grant_permission(...)
### What Was Built
1. **Account Sync Module** (230 lines)
- Automatic sync from Beancount → Castle DB
- Automatic sync from Beancount → Libra DB
- Type inference and user ID extraction
- Background scheduling support