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:
parent
cbdd5f3779
commit
4a3922895e
4 changed files with 173 additions and 10 deletions
80
views_api.py
80
views_api.py
|
|
@ -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)}"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue