Integrate account sync with API, background tasks, and user creation

Integration Components:
1. Manual API Endpoints (admin-only):
   - POST /api/v1/admin/accounts/sync (full sync)
   - POST /api/v1/admin/accounts/sync/{account_name} (single account)

2. Scheduled Background Sync:
   - Hourly background task (wait_for_account_sync)
   - Registered in castle_start() lifecycle
   - Automatically syncs new accounts from Beancount to Castle DB

3. Auto-sync on User Account Creation:
   - Updated get_or_create_user_account() in crud.py
   - Uses sync_single_account_from_beancount() for consistency
   - Ensures receivable/payable accounts are synced when users register

Flow:
- User associates wallet → creates receivable/payable in Beancount
  → syncs to Castle DB → permissions can be granted
- Admin manually syncs → all Beancount accounts added to Castle DB
- Hourly task → catches any accounts created directly in Beancount

This ensures Beancount remains the source of truth while Castle DB
maintains metadata for permissions and user associations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
padreug 2025-11-11 01:28:59 +01:00
parent cbdd5f3779
commit 4a3922895e
4 changed files with 173 additions and 10 deletions

View file

@ -3055,3 +3055,83 @@ async def api_get_account_hierarchy(
accounts_with_hierarchy.sort(key=lambda a: a.name)
return accounts_with_hierarchy
# ===== ACCOUNT SYNC ENDPOINTS =====
@castle_api_router.post("/api/v1/admin/accounts/sync")
async def api_sync_all_accounts(
force_full_sync: bool = False,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> dict:
"""
Sync all accounts from Beancount to Castle DB (admin only).
This ensures Castle DB has metadata entries for all accounts that exist
in Beancount, enabling permissions and user associations to work properly.
Args:
force_full_sync: If True, re-check all accounts. If False, only add new ones.
Returns:
Sync statistics: {total_beancount_accounts, accounts_added, accounts_skipped, errors}
"""
from .account_sync import sync_accounts_from_beancount
logger.info(f"Admin {wallet.wallet.user[:8]} triggered account sync (force={force_full_sync})")
try:
stats = await sync_accounts_from_beancount(force_full_sync=force_full_sync)
logger.info(f"Account sync complete: {stats['accounts_added']} added, {stats['accounts_skipped']} skipped")
return stats
except Exception as e:
logger.error(f"Account sync failed: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Account sync failed: {str(e)}"
)
@castle_api_router.post("/api/v1/admin/accounts/sync/{account_name:path}")
async def api_sync_single_account(
account_name: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> dict:
"""
Sync a single account from Beancount to Castle DB (admin only).
Useful for ensuring a specific account exists in Castle DB before
granting permissions on it.
Args:
account_name: Hierarchical account name (e.g., "Expenses:Food:Groceries")
Returns:
{success: bool, account_name: str, message: str}
"""
from .account_sync import sync_single_account_from_beancount
logger.info(f"Admin {wallet.wallet.user[:8]} triggered sync for account: {account_name}")
try:
created = await sync_single_account_from_beancount(account_name)
if created:
return {
"success": True,
"account_name": account_name,
"message": f"Account '{account_name}' synced successfully"
}
else:
return {
"success": False,
"account_name": account_name,
"message": f"Account '{account_name}' already exists or not found in Beancount"
}
except Exception as e:
logger.error(f"Single account sync failed for {account_name}: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Account sync failed: {str(e)}"
)