diff --git a/crud.py b/crud.py index 847da5c..85ba9cc 100644 --- a/crud.py +++ b/crud.py @@ -14,8 +14,10 @@ from .models import ( CreateJournalEntry, EntryLine, JournalEntry, + StoredUserWalletSettings, UserBalance, UserCastleSettings, + UserWalletSettings, ) db = Database("ext_castle") @@ -377,3 +379,33 @@ async def update_castle_settings( settings = UserCastleSettings(**data.dict(), id=user_id) await db.update("extension_settings", settings) return settings + + +# ===== USER WALLET SETTINGS ===== + + +async def create_user_wallet_settings( + user_id: str, data: UserWalletSettings +) -> UserWalletSettings: + settings = StoredUserWalletSettings(**data.dict(), id=user_id) + await db.insert("user_wallet_settings", settings) + return settings + + +async def get_user_wallet_settings(user_id: str) -> Optional[UserWalletSettings]: + return await db.fetchone( + """ + SELECT * FROM user_wallet_settings + WHERE id = :user_id + """, + {"user_id": user_id}, + UserWalletSettings, + ) + + +async def update_user_wallet_settings( + user_id: str, data: UserWalletSettings +) -> UserWalletSettings: + settings = StoredUserWalletSettings(**data.dict(), id=user_id) + await db.update("user_wallet_settings", settings) + return settings diff --git a/migrations.py b/migrations.py index 0c8b0ba..2144a8e 100644 --- a/migrations.py +++ b/migrations.py @@ -140,3 +140,18 @@ async def m003_extension_settings(db): ); """ ) + + +async def m004_user_wallet_settings(db): + """ + Create user_wallet_settings table for per-user wallet configuration. + """ + await db.execute( + f""" + CREATE TABLE user_wallet_settings ( + id TEXT NOT NULL PRIMARY KEY, + user_wallet_id TEXT, + updated_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} + ); + """ + ) diff --git a/models.py b/models.py index e54960a..f1aa8e6 100644 --- a/models.py +++ b/models.py @@ -119,3 +119,16 @@ class UserCastleSettings(CastleSettings): """User-specific settings (stored with user_id)""" id: str + + +class UserWalletSettings(BaseModel): + """Per-user wallet settings""" + + user_wallet_id: Optional[str] = None # The wallet ID for this specific user + updated_at: datetime = Field(default_factory=lambda: datetime.now()) + + +class StoredUserWalletSettings(UserWalletSettings): + """Stored user wallet settings with user ID""" + + id: str # user_id diff --git a/services.py b/services.py index ff3e5d6..47a3d7b 100644 --- a/services.py +++ b/services.py @@ -1,9 +1,12 @@ from .crud import ( create_castle_settings, + create_user_wallet_settings, get_castle_settings, + get_user_wallet_settings, update_castle_settings, + update_user_wallet_settings, ) -from .models import CastleSettings +from .models import CastleSettings, UserWalletSettings async def get_settings(user_id: str) -> CastleSettings: @@ -21,3 +24,22 @@ async def update_settings(user_id: str, data: CastleSettings) -> CastleSettings: settings = await update_castle_settings(user_id, data) return settings + + +async def get_user_wallet(user_id: str) -> UserWalletSettings: + settings = await get_user_wallet_settings(user_id) + if not settings: + settings = await create_user_wallet_settings(user_id, UserWalletSettings()) + return settings + + +async def update_user_wallet( + user_id: str, data: UserWalletSettings +) -> UserWalletSettings: + settings = await get_user_wallet_settings(user_id) + if not settings: + settings = await create_user_wallet_settings(user_id, data) + else: + settings = await update_user_wallet_settings(user_id, data) + + return settings diff --git a/static/js/index.js b/static/js/index.js index 407907a..a5eb17e 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -12,9 +12,11 @@ window.app = Vue.createApp({ accounts: [], currencies: [], settings: null, + userWalletSettings: null, isAdmin: false, isSuperUser: false, castleWalletConfigured: false, + userWalletConfigured: false, expenseDialog: { show: false, description: '', @@ -34,6 +36,11 @@ window.app = Vue.createApp({ show: false, castleWalletId: '', loading: false + }, + userWalletDialog: { + show: false, + userWalletId: '', + loading: false } } }, @@ -123,10 +130,27 @@ window.app = Vue.createApp({ this.castleWalletConfigured = false } }, + async loadUserWallet() { + try { + const response = await LNbits.api.request( + 'GET', + '/castle/api/v1/user/wallet', + this.g.user.wallets[0].inkey + ) + this.userWalletSettings = response.data + this.userWalletConfigured = !!(this.userWalletSettings && this.userWalletSettings.user_wallet_id) + } catch (error) { + this.userWalletConfigured = false + } + }, showSettingsDialog() { this.settingsDialog.castleWalletId = this.settings?.castle_wallet_id || '' this.settingsDialog.show = true }, + showUserWalletDialog() { + this.userWalletDialog.userWalletId = this.userWalletSettings?.user_wallet_id || '' + this.userWalletDialog.show = true + }, async submitSettings() { if (!this.settingsDialog.castleWalletId) { this.$q.notify({ @@ -158,6 +182,37 @@ window.app = Vue.createApp({ this.settingsDialog.loading = false } }, + async submitUserWallet() { + if (!this.userWalletDialog.userWalletId) { + this.$q.notify({ + type: 'warning', + message: 'Wallet ID is required' + }) + return + } + + this.userWalletDialog.loading = true + try { + await LNbits.api.request( + 'PUT', + '/castle/api/v1/user/wallet', + this.g.user.wallets[0].inkey, + { + user_wallet_id: this.userWalletDialog.userWalletId + } + ) + this.$q.notify({ + type: 'positive', + message: 'Wallet configured successfully' + }) + this.userWalletDialog.show = false + await this.loadUserWallet() + } catch (error) { + LNbits.utils.notifyApiError(error) + } finally { + this.userWalletDialog.loading = false + } + }, async submitExpense() { this.expenseDialog.loading = true try { @@ -252,5 +307,6 @@ window.app = Vue.createApp({ await this.loadAccounts() await this.loadCurrencies() await this.loadSettings() + await this.loadUserWallet() } }) diff --git a/templates/castle/index.html b/templates/castle/index.html index 07c0340..a39bdae 100644 --- a/templates/castle/index.html +++ b/templates/castle/index.html @@ -16,9 +16,12 @@
Track expenses, receivables, and balances for the collective
-