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

View file

@ -32,7 +32,7 @@ window.app = Vue.createApp({
isAdmin: false,
isSuperUser: false,
settingsLoaded: false, // Flag to prevent race conditions on toolbar buttons
castleWalletConfigured: false,
libraWalletConfigured: false,
userWalletConfigured: false,
syncingAccounts: false,
currentExchangeRate: null, // BTC/EUR rate (sats per EUR)
@ -58,9 +58,9 @@ window.app = Vue.createApp({
},
settingsDialog: {
show: false,
castleWalletId: '',
libraWalletId: '',
favaUrl: 'http://localhost:3333',
favaLedgerSlug: 'castle-ledger',
favaLedgerSlug: 'libra-ledger',
favaTimeout: 10.0,
loading: false
},
@ -208,8 +208,8 @@ window.app = Vue.createApp({
accountTypeOptions() {
return [
{ label: 'All Types', value: null },
{ label: 'Receivable (User owes Castle)', value: 'asset' },
{ label: 'Payable (Castle owes User)', value: 'liability' },
{ label: 'Receivable (User owes Libra)', value: 'asset' },
{ label: 'Payable (Libra owes User)', value: 'liability' },
{ label: 'Equity (User Balance)', value: 'equity' }
]
},
@ -318,7 +318,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/balance',
'/libra/api/v1/balance',
this.g.user.wallets[0].inkey
)
this.balance = response.data
@ -341,7 +341,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/balances/all',
'/libra/api/v1/balances/all',
this.g.user.wallets[0].adminkey
)
this.allUserBalances = response.data
@ -389,7 +389,7 @@ window.app = Vue.createApp({
const response = await LNbits.api.request(
'GET',
`/castle/api/v1/entries/user?${queryParams}`,
`/libra/api/v1/entries/user?${queryParams}`,
this.g.user.wallets[0].inkey
)
@ -458,7 +458,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/accounts?filter_by_user=true&exclude_virtual=true',
'/libra/api/v1/accounts?filter_by_user=true&exclude_virtual=true',
this.g.user.wallets[0].inkey
)
this.accounts = response.data
@ -472,7 +472,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/currencies',
'/libra/api/v1/currencies',
this.g.user.wallets[0].inkey
)
this.currencies = response.data
@ -484,7 +484,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/users',
'/libra/api/v1/users',
this.g.user.wallets[0].adminkey
)
this.users = response.data
@ -496,7 +496,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/user/info',
'/libra/api/v1/user/info',
this.g.user.wallets[0].inkey
)
this.userInfo = response.data
@ -510,18 +510,18 @@ window.app = Vue.createApp({
// Try with admin key first to check settings
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/settings',
'/libra/api/v1/settings',
this.g.user.wallets[0].inkey
)
this.settings = response.data
this.castleWalletConfigured = !!(this.settings && this.settings.castle_wallet_id)
this.libraWalletConfigured = !!(this.settings && this.settings.libra_wallet_id)
// Check if user is super user by seeing if they can access admin features
this.isSuperUser = this.g.user.super_user || false
this.isAdmin = this.g.user.admin || this.isSuperUser
} catch (error) {
// Settings not available
this.castleWalletConfigured = false
this.libraWalletConfigured = false
} finally {
// Mark settings as loaded to enable toolbar buttons
this.settingsLoaded = true
@ -531,7 +531,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/user/wallet',
'/libra/api/v1/user/wallet',
this.g.user.wallets[0].inkey
)
this.userWalletSettings = response.data
@ -545,7 +545,7 @@ window.app = Vue.createApp({
try {
const {data} = await LNbits.api.request(
'POST',
'/castle/api/v1/admin/accounts/sync',
'/libra/api/v1/admin/accounts/sync',
this.g.user.wallets[0].adminkey
)
const errors = (data?.errors || []).length
@ -567,9 +567,9 @@ window.app = Vue.createApp({
}
},
showSettingsDialog() {
this.settingsDialog.castleWalletId = this.settings?.castle_wallet_id || ''
this.settingsDialog.libraWalletId = this.settings?.libra_wallet_id || ''
this.settingsDialog.favaUrl = this.settings?.fava_url || 'http://localhost:3333'
this.settingsDialog.favaLedgerSlug = this.settings?.fava_ledger_slug || 'castle-ledger'
this.settingsDialog.favaLedgerSlug = this.settings?.fava_ledger_slug || 'libra-ledger'
this.settingsDialog.favaTimeout = this.settings?.fava_timeout || 10.0
this.settingsDialog.show = true
},
@ -578,10 +578,10 @@ window.app = Vue.createApp({
this.userWalletDialog.show = true
},
async submitSettings() {
if (!this.settingsDialog.castleWalletId) {
if (!this.settingsDialog.libraWalletId) {
this.$q.notify({
type: 'warning',
message: 'Castle Wallet ID is required'
message: 'Libra Wallet ID is required'
})
return
}
@ -598,12 +598,12 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'PUT',
'/castle/api/v1/settings',
'/libra/api/v1/settings',
this.g.user.wallets[0].adminkey,
{
castle_wallet_id: this.settingsDialog.castleWalletId,
libra_wallet_id: this.settingsDialog.libraWalletId,
fava_url: this.settingsDialog.favaUrl,
fava_ledger_slug: this.settingsDialog.favaLedgerSlug || 'castle-ledger',
fava_ledger_slug: this.settingsDialog.favaLedgerSlug || 'libra-ledger',
fava_timeout: parseFloat(this.settingsDialog.favaTimeout) || 10.0
}
)
@ -613,7 +613,7 @@ window.app = Vue.createApp({
})
this.settingsDialog.show = false
await this.loadSettings()
// Reload user wallet to reflect castle wallet for super user
// Reload user wallet to reflect libra wallet for super user
if (this.isSuperUser) {
await this.loadUserWallet()
}
@ -636,7 +636,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'PUT',
'/castle/api/v1/user/wallet',
'/libra/api/v1/user/wallet',
this.g.user.wallets[0].inkey,
{
user_wallet_id: this.userWalletDialog.userWalletId
@ -659,7 +659,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
'/castle/api/v1/entries/expense',
'/libra/api/v1/entries/expense',
this.g.user.wallets[0].inkey,
{
description: this.expenseDialog.description,
@ -696,10 +696,10 @@ window.app = Vue.createApp({
}
try {
// Generate an invoice on the Castle wallet
// Generate an invoice on the Libra wallet
const response = await LNbits.api.request(
'POST',
'/castle/api/v1/generate-payment-invoice',
'/libra/api/v1/generate-payment-invoice',
this.g.user.wallets[0].inkey,
{
amount: this.payDialog.amount
@ -745,7 +745,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
'/castle/api/v1/record-payment',
'/libra/api/v1/record-payment',
this.g.user.wallets[0].inkey,
{
payment_hash: paymentHash
@ -788,15 +788,15 @@ window.app = Vue.createApp({
},
showManualPaymentOption() {
// This is for when user wants to pay their debt manually
// For now, just notify them to contact castle
// For now, just notify them to contact libra
this.$q.notify({
type: 'info',
message: 'Please contact Castle directly to arrange manual payment.',
message: 'Please contact Libra directly to arrange manual payment.',
timeout: 3000
})
},
showManualPaymentDialog() {
// This is for when Castle owes user and they want to request manual payment
// This is for when Libra owes user and they want to request manual payment
this.manualPaymentDialog.amount = Math.abs(this.balance.balance)
this.manualPaymentDialog.description = ''
this.manualPaymentDialog.show = true
@ -806,7 +806,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
'/castle/api/v1/manual-payment-request',
'/libra/api/v1/manual-payment-request',
this.g.user.wallets[0].inkey,
{
amount: this.manualPaymentDialog.amount,
@ -831,8 +831,8 @@ window.app = Vue.createApp({
try {
// If super user, load all requests; otherwise load user's own requests
const endpoint = this.isSuperUser
? '/castle/api/v1/manual-payment-requests/all'
: '/castle/api/v1/manual-payment-requests'
? '/libra/api/v1/manual-payment-requests/all'
: '/libra/api/v1/manual-payment-requests'
const key = this.isSuperUser
? this.g.user.wallets[0].adminkey
: this.g.user.wallets[0].inkey
@ -855,7 +855,7 @@ window.app = Vue.createApp({
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/entries/pending',
'/libra/api/v1/entries/pending',
this.g.user.wallets[0].adminkey
)
this.pendingExpenses = response.data
@ -867,7 +867,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
`/castle/api/v1/manual-payment-requests/${requestId}/approve`,
`/libra/api/v1/manual-payment-requests/${requestId}/approve`,
this.g.user.wallets[0].adminkey
)
this.$q.notify({
@ -885,7 +885,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
`/castle/api/v1/manual-payment-requests/${requestId}/reject`,
`/libra/api/v1/manual-payment-requests/${requestId}/reject`,
this.g.user.wallets[0].adminkey
)
this.$q.notify({
@ -901,7 +901,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
`/castle/api/v1/entries/${entryId}/approve`,
`/libra/api/v1/entries/${entryId}/approve`,
this.g.user.wallets[0].adminkey
)
this.$q.notify({
@ -920,7 +920,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
`/castle/api/v1/entries/${entryId}/reject`,
`/libra/api/v1/entries/${entryId}/reject`,
this.g.user.wallets[0].adminkey
)
this.$q.notify({
@ -939,7 +939,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/assertions',
'/libra/api/v1/assertions',
this.g.user.wallets[0].adminkey
)
this.balanceAssertions = response.data
@ -965,7 +965,7 @@ window.app = Vue.createApp({
await LNbits.api.request(
'POST',
'/castle/api/v1/assertions',
'/libra/api/v1/assertions',
this.g.user.wallets[0].adminkey,
payload
)
@ -1014,7 +1014,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
`/castle/api/v1/assertions/${assertionId}/check`,
`/libra/api/v1/assertions/${assertionId}/check`,
this.g.user.wallets[0].adminkey
)
@ -1033,7 +1033,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'DELETE',
`/castle/api/v1/assertions/${assertionId}`,
`/libra/api/v1/assertions/${assertionId}`,
this.g.user.wallets[0].adminkey
)
@ -1062,7 +1062,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/reconciliation/summary',
'/libra/api/v1/reconciliation/summary',
this.g.user.wallets[0].adminkey
)
this.reconciliation.summary = response.data
@ -1076,7 +1076,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/reconciliation/discrepancies',
'/libra/api/v1/reconciliation/discrepancies',
this.g.user.wallets[0].adminkey
)
this.reconciliation.discrepancies = response.data
@ -1089,7 +1089,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'POST',
'/castle/api/v1/reconciliation/check-all',
'/libra/api/v1/reconciliation/check-all',
this.g.user.wallets[0].adminkey
)
@ -1143,7 +1143,7 @@ window.app = Vue.createApp({
try {
await LNbits.api.request(
'POST',
'/castle/api/v1/entries/receivable',
'/libra/api/v1/entries/receivable',
this.g.user.wallets[0].adminkey,
{
description: this.receivableDialog.description,
@ -1186,7 +1186,7 @@ window.app = Vue.createApp({
this.receivableDialog.currency = null
},
async showSettleReceivableDialog(userBalance) {
// Only show for users who owe castle (positive balance = receivable)
// Only show for users who owe libra (positive balance = receivable)
if (userBalance.balance <= 0) return
// Clear any existing polling
@ -1202,19 +1202,19 @@ window.app = Vue.createApp({
// Fetch unsettled entries for this user (BOTH receivables AND expenses for net settlement)
let allEntryLinks = []
try {
// Fetch receivable entries (user owes castle)
// Fetch receivable entries (user owes libra)
const receivableResponse = await LNbits.api.request(
'GET',
`/castle/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=receivable`,
`/libra/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=receivable`,
this.g.user.wallets[0].adminkey
)
const receivableEntries = receivableResponse.data.unsettled_entries || []
allEntryLinks.push(...receivableEntries.map(e => e.link).filter(l => l))
// Also fetch expense entries (castle owes user) - these are netted in the settlement
// Also fetch expense entries (libra owes user) - these are netted in the settlement
const expenseResponse = await LNbits.api.request(
'GET',
`/castle/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=expense`,
`/libra/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=expense`,
this.g.user.wallets[0].adminkey
)
const expenseEntries = expenseResponse.data.unsettled_entries || []
@ -1254,10 +1254,10 @@ window.app = Vue.createApp({
}
try {
// Generate an invoice on the Castle wallet for the user to pay
// Generate an invoice on the Libra wallet for the user to pay
const response = await LNbits.api.request(
'POST',
'/castle/api/v1/generate-payment-invoice',
'/libra/api/v1/generate-payment-invoice',
this.g.user.wallets[0].adminkey,
{
amount: this.settleReceivableDialog.amount,
@ -1384,7 +1384,7 @@ window.app = Vue.createApp({
const response = await LNbits.api.request(
'POST',
'/castle/api/v1/receivables/settle',
'/libra/api/v1/receivables/settle',
this.g.user.wallets[0].adminkey,
payload
)
@ -1408,7 +1408,7 @@ window.app = Vue.createApp({
}
},
async showPayUserDialog(userBalance) {
// Only show for users castle owes (negative balance = payable)
// Only show for users libra owes (negative balance = payable)
if (userBalance.balance >= 0) return
// Extract fiat balances (e.g., EUR)
@ -1416,26 +1416,26 @@ window.app = Vue.createApp({
const fiatCurrency = Object.keys(fiatBalances)[0] || null
const fiatAmount = fiatCurrency ? fiatBalances[fiatCurrency] : 0
// Use absolute values since balance is negative (liability = castle owes user)
// Use absolute values since balance is negative (liability = libra owes user)
const maxAmountSats = Math.abs(userBalance.balance)
const maxAmountFiat = Math.abs(fiatAmount)
// Fetch unsettled entries for this user (BOTH expenses AND receivables for net settlement)
let allEntryLinks = []
try {
// Fetch expense entries (castle owes user)
// Fetch expense entries (libra owes user)
const expenseResponse = await LNbits.api.request(
'GET',
`/castle/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=expense`,
`/libra/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=expense`,
this.g.user.wallets[0].adminkey
)
const expenseEntries = expenseResponse.data.unsettled_entries || []
allEntryLinks.push(...expenseEntries.map(e => e.link).filter(l => l))
// Also fetch receivable entries (user owes castle) - these are netted in the settlement
// Also fetch receivable entries (user owes libra) - these are netted in the settlement
const receivableResponse = await LNbits.api.request(
'GET',
`/castle/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=receivable`,
`/libra/api/v1/users/${userBalance.user_id}/unsettled-entries?entry_type=receivable`,
this.g.user.wallets[0].adminkey
)
const receivableEntries = receivableResponse.data.unsettled_entries || []
@ -1448,7 +1448,7 @@ window.app = Vue.createApp({
show: true,
user_id: userBalance.user_id,
username: userBalance.username,
maxAmount: maxAmountSats, // Positive sats amount castle owes
maxAmount: maxAmountSats, // Positive sats amount libra owes
maxAmountFiat: maxAmountFiat, // EUR or other fiat amount (positive)
fiatCurrency: fiatCurrency,
amount: maxAmountSats, // Default to sats since lightning is the default payment method
@ -1480,14 +1480,14 @@ window.app = Vue.createApp({
{
out: false,
amount: this.payUserDialog.amount,
memo: `Payment from Castle to ${this.payUserDialog.username}`
memo: `Payment from Libra to ${this.payUserDialog.username}`
}
)
console.log(invoiceResponse)
const paymentRequest = invoiceResponse.data.bolt11
// Pay the invoice from Castle's wallet
// Pay the invoice from Libra's wallet
const paymentResponse = await LNbits.api.request(
'POST',
`/api/v1/payments`,
@ -1498,7 +1498,7 @@ window.app = Vue.createApp({
}
)
// Record the payment in Castle accounting
// Record the payment in Libra accounting
const payPayload = {
user_id: this.payUserDialog.user_id,
amount: this.payUserDialog.amount,
@ -1513,7 +1513,7 @@ window.app = Vue.createApp({
await LNbits.api.request(
'POST',
'/castle/api/v1/payables/pay',
'/libra/api/v1/payables/pay',
this.g.user.wallets[0].adminkey,
payPayload
)
@ -1579,7 +1579,7 @@ window.app = Vue.createApp({
const response = await LNbits.api.request(
'POST',
'/castle/api/v1/payables/pay',
'/libra/api/v1/payables/pay',
this.g.user.wallets[0].adminkey,
payload
)
@ -1606,7 +1606,7 @@ window.app = Vue.createApp({
try {
const response = await LNbits.api.request(
'GET',
`/castle/api/v1/user-wallet/${userId}`,
`/libra/api/v1/user-wallet/${userId}`,
this.g.user.wallets[0].adminkey
)
return response.data
@ -1663,13 +1663,13 @@ window.app = Vue.createApp({
return null
},
isReceivable(entry) {
// Check if this is a receivable entry (user owes castle)
// Check if this is a receivable entry (user owes libra)
if (entry.tags && entry.tags.includes('receivable-entry')) return true
if (entry.account && entry.account.includes('Receivable')) return true
return false
},
isPayable(entry) {
// Check if this is a payable entry (castle owes user)
// Check if this is a payable entry (libra owes user)
if (entry.tags && entry.tags.includes('expense-entry')) return true
if (entry.account && entry.account.includes('Payable')) return true
return false