The original "find last Open directive, insert after its metadata" logic
was a clever optimisation for the monolithic ledger where opens, txns,
and assertions all lived in one file -- you wanted new opens grouped with
existing opens, not appended after a long transaction tail.
Post-split, each include file has one mutation profile:
- accounts/chart.beancount: only Open directives
- accounts/users.beancount: only Open directives
- transactions.beancount: only Transactions
There is no longer a content shape that benefits from mid-file insertion;
the existing heuristic also had a pre-existing bug where it only matched
'open ' OR '{current_year}-' as line prefixes, so 1970-* seed opens were
invisible and the search "stuck" to the first current-year line in the
file (which on aio-demo ended up being the wrong place).
Drop the search; always append. Simpler, chronological, append-only
friendly.
Refs: aiolabs/libra#28
Three small fixes shaken out by live testing on aio-demo:
1. fava_client.add_account: when the target file has no Open directives
yet (e.g. the empty accounts/users.beancount seed), append at end of
file instead of inserting at index 0. Keeps the seed header comments
at the top where they belong.
2. account_sync.sync_single_account_from_beancount: read the full user_id
from Beancount metadata when present, fall back to the name-derived
8-char prefix otherwise. crud.get_or_create_user_account writes the
full 32-char user_id into Beancount metadata when creating per-user
accounts; the sync function was only looking at the account name and
returning the prefix, so the post-sync `WHERE user_id=:user_id` query
in crud.py missed the row and fell through the UNIQUE-constraint
recovery path. Three lines of warning noise per user-account creation.
3. tasks.wait_for_account_sync: await `wait_for_fava_client()` (new
helper backed by an asyncio.Event in fava_client.py) before the first
sync iteration. Previously the sync task started in libra_start()
raced the fire-and-forget `_init_fava()` coroutine and reliably
crashed the first run with "Fava client not initialized".
Refs: aiolabs/libra#28
Fava's /api/source endpoint rejects relative paths with HTTP 500
(NonSourceFileError: "Trying to read a non-source file at '...'"). The
include-aware `_infer_target_file` helper returns relative paths
(e.g. "accounts/users.beancount"), so add a `_resolve_target_file`
hook that prepends the ledger root directory.
The dirname is derived from a one-time GET /api/options and cached on
the FavaClient instance (which is a module-level singleton), guarded by
an asyncio.Lock so concurrent first-callers don't double-fetch.
Absolute paths pass through unchanged, so the admin endpoint that
explicitly passes target_file="accounts/chart.beancount" works the same
as one that passes "/var/lib/fava/accounts/chart.beancount".
Verified against aio-demo's live fava: relative paths now produce
HTTP 200 reads on options.beancount, accounts/chart.beancount,
accounts/users.beancount, and transactions.beancount.
Refs: aiolabs/libra#28
Companion to the fava ledger split (aiolabs/server-deploy#4). Super-user
endpoint that adds a new Open directive to accounts/chart.beancount via
fava_client.add_account (explicit target_file), then mirrors the account
into Libra's DB via sync_single_account_from_beancount so permissions can
be granted on it.
Validates the account name against the five Beancount top-level prefixes
(Assets:/Liabilities:/Equity:/Income:/Expenses:) and returns 400 on a bad
prefix.
Per-user accounts (matching :User-xxxxxxxx) keep their existing code path
via crud.get_or_create_user_account, which inherits the inferred target_file
(accounts/users.beancount) from the add_account default.
Backend only -- the LNbits admin UI on top is tracked separately as
aiolabs/libra#30.
Refs: aiolabs/libra#29
The Fava-backed ledger is being split into purpose-specific files (see
aiolabs/server-deploy#4): accounts/chart.beancount for static + admin-managed
opens, accounts/users.beancount for libra-appended per-user opens.
Add a `target_file` parameter to `add_account` that defaults to inference
from the account name (`:User-[0-9a-f]{8}$` -> users.beancount, otherwise
chart.beancount). Drop the now-redundant `GET /api/options` call that was
only used to discover the root file path. Callers that need explicit
control (e.g. the upcoming admin chart-edit endpoint) can pass
`target_file=` directly.
The retry loop, write lock, and insertion-point search are unchanged --
each included file is a self-contained source the existing logic operates
on cleanly.
Refs: aiolabs/libra#28