From 4a3d2012be5a2a39f75e23258f643ba63fc40b48 Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 7 Sep 2025 01:58:36 +0200 Subject: [PATCH] Complete LnbitsAPI migration to dependency injection pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Convert LnbitsAPI from singleton to BaseService extension - Add LNBITS_API service token to DI container - Register LnbitsAPI service in base module with proper initialization order - Update AuthService to depend on injected LnbitsAPI instead of singleton - Fix BaseService to properly track LnbitsAPI dependency in getMissingDependencies - Update events API functions to use dependency injection - Resolve initialization timing issue preventing application startup 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/core/base/BaseService.ts | 5 +++++ src/core/di-container.ts | 3 +++ src/lib/api/events.ts | 9 ++++++++- src/lib/api/lnbits.ts | 21 ++++++++++++++++++-- src/modules/base/auth/auth-service.ts | 28 +++++++++++++-------------- src/modules/base/index.ts | 21 +++++++++++++++++++- 6 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/core/base/BaseService.ts b/src/core/base/BaseService.ts index 812a462..949eefd 100644 --- a/src/core/base/BaseService.ts +++ b/src/core/base/BaseService.ts @@ -48,6 +48,7 @@ export abstract class BaseService { protected storageService: any = null protected toastService: any = null protected invoiceService: any = null + protected lnbitsAPI: any = null // Service state public readonly isInitialized: Ref = ref(false) @@ -140,6 +141,7 @@ export abstract class BaseService { this.storageService = tryInjectService(SERVICE_TOKENS.STORAGE_SERVICE) this.toastService = tryInjectService(SERVICE_TOKENS.TOAST_SERVICE) this.invoiceService = tryInjectService(SERVICE_TOKENS.INVOICE_SERVICE) + this.lnbitsAPI = tryInjectService(SERVICE_TOKENS.LNBITS_API) // Check if all required dependencies are available const missingDeps = this.getMissingDependencies() @@ -193,6 +195,9 @@ export abstract class BaseService { if (deps.includes('ToastService') && !this.toastService) { missing.push('ToastService') } + if (deps.includes('LnbitsAPI') && !this.lnbitsAPI) { + missing.push('LnbitsAPI') + } return missing } diff --git a/src/core/di-container.ts b/src/core/di-container.ts index ea58e1a..7e494d2 100644 --- a/src/core/di-container.ts +++ b/src/core/di-container.ts @@ -140,6 +140,9 @@ export const SERVICE_TOKENS = { // Nostrmarket services NOSTRMARKET_SERVICE: Symbol('nostrmarketService'), + + // API services + LNBITS_API: Symbol('lnbitsAPI'), } as const // Type-safe injection helpers diff --git a/src/lib/api/events.ts b/src/lib/api/events.ts index 3c6d7ea..dbb783c 100644 --- a/src/lib/api/events.ts +++ b/src/lib/api/events.ts @@ -1,6 +1,7 @@ import type { Event, Ticket } from '../types/event' import { config } from '@/lib/config' -import { lnbitsAPI } from './lnbits' +import { injectService, SERVICE_TOKENS } from '@/core/di-container' +import type { LnbitsAPI } from './lnbits' const API_BASE_URL = config.api.baseUrl || 'http://lnbits' const API_KEY = config.api.key @@ -39,6 +40,9 @@ export async function fetchEvents(): Promise { export async function purchaseTicket(eventId: string): Promise<{ payment_hash: string; payment_request: string }> { try { + // Get injected LnbitsAPI service + const lnbitsAPI = injectService(SERVICE_TOKENS.LNBITS_API) as LnbitsAPI + // Get current user to ensure authentication const user = await lnbitsAPI.getCurrentUser() if (!user) { @@ -135,6 +139,9 @@ export async function checkPaymentStatus(eventId: string, paymentHash: string): export async function fetchUserTickets(userId: string): Promise { try { + // Get injected LnbitsAPI service + const lnbitsAPI = injectService(SERVICE_TOKENS.LNBITS_API) as LnbitsAPI + const response = await fetch( `${API_BASE_URL}/events/api/v1/tickets/user/${userId}`, { diff --git a/src/lib/api/lnbits.ts b/src/lib/api/lnbits.ts index 4e1f574..1166cc8 100644 --- a/src/lib/api/lnbits.ts +++ b/src/lib/api/lnbits.ts @@ -61,16 +61,33 @@ interface User { } } +import { BaseService } from '@/core/base/BaseService' import { getApiUrl, getAuthToken, setAuthToken, removeAuthToken } from '@/lib/config/lnbits' -class LnbitsAPI { +export class LnbitsAPI extends BaseService { + // Service metadata + protected readonly metadata = { + name: 'LnbitsAPI', + version: '1.0.0', + dependencies: [] // No dependencies - this is a core infrastructure service + } + private accessToken: string | null = null constructor() { + super() // Try to load token from localStorage this.accessToken = getAuthToken() } + /** + * Service-specific initialization (called by BaseService) + */ + protected async onInitialize(): Promise { + this.debug('LnbitsAPI initialized') + // Service is ready to use + } + private async request( endpoint: string, options: RequestInit = {} @@ -183,5 +200,5 @@ class LnbitsAPI { } } -export const lnbitsAPI = new LnbitsAPI() +// Service is now registered in the DI container export type { LoginCredentials, RegisterData, AuthResponse, User } \ No newline at end of file diff --git a/src/modules/base/auth/auth-service.ts b/src/modules/base/auth/auth-service.ts index c7c6d13..decf10c 100644 --- a/src/modules/base/auth/auth-service.ts +++ b/src/modules/base/auth/auth-service.ts @@ -2,14 +2,14 @@ import { ref, computed } from 'vue' import { BaseService } from '@/core/base/BaseService' import { eventBus } from '@/core/event-bus' -import { lnbitsAPI, type LoginCredentials, type RegisterData, type User } from '@/lib/api/lnbits' +import type { LoginCredentials, RegisterData, User } from '@/lib/api/lnbits' export class AuthService extends BaseService { // Service metadata protected readonly metadata = { name: 'AuthService', version: '1.0.0', - dependencies: [] // Auth service has no dependencies on other services + dependencies: ['LnbitsAPI'] // Auth service depends on LnbitsAPI for authentication } // Public state @@ -47,7 +47,7 @@ export class AuthService extends BaseService { } async checkAuth(): Promise { - if (!lnbitsAPI.isAuthenticated()) { + if (!this.lnbitsAPI.isAuthenticated()) { this.debug('No auth token found - user needs to login') this.isAuthenticated.value = false this.user.value = null @@ -56,7 +56,7 @@ export class AuthService extends BaseService { try { this.isLoading.value = true - const userData = await lnbitsAPI.getCurrentUser() + const userData = await this.lnbitsAPI.getCurrentUser() this.user.value = userData this.isAuthenticated.value = true @@ -70,7 +70,7 @@ export class AuthService extends BaseService { this.isAuthenticated.value = false this.user.value = null // Clear invalid token - lnbitsAPI.logout() + this.lnbitsAPI.logout() return false } finally { this.isLoading.value = false @@ -81,8 +81,8 @@ export class AuthService extends BaseService { this.isLoading.value = true try { - await lnbitsAPI.login(credentials) - const userData = await lnbitsAPI.getCurrentUser() + await this.lnbitsAPI.login(credentials) + const userData = await this.lnbitsAPI.getCurrentUser() this.user.value = userData this.isAuthenticated.value = true @@ -102,8 +102,8 @@ export class AuthService extends BaseService { this.isLoading.value = true try { - await lnbitsAPI.register(data) - const userData = await lnbitsAPI.getCurrentUser() + await this.lnbitsAPI.register(data) + const userData = await this.lnbitsAPI.getCurrentUser() this.user.value = userData this.isAuthenticated.value = true @@ -120,7 +120,7 @@ export class AuthService extends BaseService { } async logout(): Promise { - lnbitsAPI.logout() + this.lnbitsAPI.logout() this.user.value = null this.isAuthenticated.value = false this.error.value = null @@ -134,14 +134,14 @@ export class AuthService extends BaseService { } async initialize(): Promise { - // Alias for checkAuth for compatibility - await this.checkAuth() + // Call BaseService initialize first to inject dependencies + await super.initialize() } async updatePassword(currentPassword: string, newPassword: string): Promise { try { this.isLoading.value = true - const updatedUser = await lnbitsAPI.updatePassword(currentPassword, newPassword) + const updatedUser = await this.lnbitsAPI.updatePassword(currentPassword, newPassword) this.user.value = updatedUser } catch (error) { const err = this.handleError(error, 'updatePassword') @@ -154,7 +154,7 @@ export class AuthService extends BaseService { async updateProfile(data: Partial): Promise { try { this.isLoading.value = true - const updatedUser = await lnbitsAPI.updateProfile(data) + const updatedUser = await this.lnbitsAPI.updateProfile(data) this.user.value = updatedUser } catch (error) { const err = this.handleError(error, 'updateProfile') diff --git a/src/modules/base/index.ts b/src/modules/base/index.ts index 080a954..f518528 100644 --- a/src/modules/base/index.ts +++ b/src/modules/base/index.ts @@ -15,9 +15,11 @@ import { visibilityService } from '@/core/services/VisibilityService' import { storageService } from '@/core/services/StorageService' import { toastService } from '@/core/services/ToastService' import { InvoiceService } from '@/core/services/invoiceService' +import { LnbitsAPI } from '@/lib/api/lnbits' // Create service instances const invoiceService = new InvoiceService() +const lnbitsAPI = new LnbitsAPI() /** * Base Module Plugin @@ -51,12 +53,22 @@ export const baseModule: ModulePlugin = { // Register invoice service container.provide(SERVICE_TOKENS.INVOICE_SERVICE, invoiceService) + // Register API services + container.provide(SERVICE_TOKENS.LNBITS_API, lnbitsAPI) + // Register PWA service container.provide('pwaService', pwaService) - // Initialize core services + // Initialize core services in dependency order relayHub.setRelayUrls(options?.config?.nostr?.relays || []) await relayHub.initialize() + + // Initialize LnbitsAPI first since AuthService depends on it + await lnbitsAPI.initialize({ + waitForDependencies: false, // LnbitsAPI is core infrastructure with no dependencies + maxRetries: 1 + }) + // Auth initialization moved to app.ts before router guards await paymentService.initialize({ waitForDependencies: true, // PaymentService depends on AuthService @@ -74,6 +86,7 @@ export const baseModule: ModulePlugin = { waitForDependencies: false, // ToastService has no dependencies maxRetries: 1 }) + // InvoiceService doesn't need initialization as it's not a BaseService console.log('✅ Base module installed successfully') }, @@ -88,6 +101,12 @@ export const baseModule: ModulePlugin = { await visibilityService.dispose() await storageService.dispose() await toastService.dispose() + // InvoiceService doesn't need disposal as it's not a BaseService + await lnbitsAPI.dispose() + + // Remove services from DI container + container.remove(SERVICE_TOKENS.LNBITS_API) + container.remove(SERVICE_TOKENS.INVOICE_SERVICE) console.log('✅ Base module uninstalled') },