Complete LnbitsAPI migration to dependency injection pattern

- 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 <noreply@anthropic.com>
This commit is contained in:
padreug 2025-09-07 01:58:36 +02:00
parent 093846b351
commit 4a3d2012be
6 changed files with 69 additions and 18 deletions

View file

@ -48,6 +48,7 @@ export abstract class BaseService {
protected storageService: any = null protected storageService: any = null
protected toastService: any = null protected toastService: any = null
protected invoiceService: any = null protected invoiceService: any = null
protected lnbitsAPI: any = null
// Service state // Service state
public readonly isInitialized: Ref<boolean> = ref(false) public readonly isInitialized: Ref<boolean> = ref(false)
@ -140,6 +141,7 @@ export abstract class BaseService {
this.storageService = tryInjectService(SERVICE_TOKENS.STORAGE_SERVICE) this.storageService = tryInjectService(SERVICE_TOKENS.STORAGE_SERVICE)
this.toastService = tryInjectService(SERVICE_TOKENS.TOAST_SERVICE) this.toastService = tryInjectService(SERVICE_TOKENS.TOAST_SERVICE)
this.invoiceService = tryInjectService(SERVICE_TOKENS.INVOICE_SERVICE) this.invoiceService = tryInjectService(SERVICE_TOKENS.INVOICE_SERVICE)
this.lnbitsAPI = tryInjectService(SERVICE_TOKENS.LNBITS_API)
// Check if all required dependencies are available // Check if all required dependencies are available
const missingDeps = this.getMissingDependencies() const missingDeps = this.getMissingDependencies()
@ -193,6 +195,9 @@ export abstract class BaseService {
if (deps.includes('ToastService') && !this.toastService) { if (deps.includes('ToastService') && !this.toastService) {
missing.push('ToastService') missing.push('ToastService')
} }
if (deps.includes('LnbitsAPI') && !this.lnbitsAPI) {
missing.push('LnbitsAPI')
}
return missing return missing
} }

View file

@ -140,6 +140,9 @@ export const SERVICE_TOKENS = {
// Nostrmarket services // Nostrmarket services
NOSTRMARKET_SERVICE: Symbol('nostrmarketService'), NOSTRMARKET_SERVICE: Symbol('nostrmarketService'),
// API services
LNBITS_API: Symbol('lnbitsAPI'),
} as const } as const
// Type-safe injection helpers // Type-safe injection helpers

View file

@ -1,6 +1,7 @@
import type { Event, Ticket } from '../types/event' import type { Event, Ticket } from '../types/event'
import { config } from '@/lib/config' 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_BASE_URL = config.api.baseUrl || 'http://lnbits'
const API_KEY = config.api.key const API_KEY = config.api.key
@ -39,6 +40,9 @@ export async function fetchEvents(): Promise<Event[]> {
export async function purchaseTicket(eventId: string): Promise<{ payment_hash: string; payment_request: string }> { export async function purchaseTicket(eventId: string): Promise<{ payment_hash: string; payment_request: string }> {
try { try {
// Get injected LnbitsAPI service
const lnbitsAPI = injectService(SERVICE_TOKENS.LNBITS_API) as LnbitsAPI
// Get current user to ensure authentication // Get current user to ensure authentication
const user = await lnbitsAPI.getCurrentUser() const user = await lnbitsAPI.getCurrentUser()
if (!user) { if (!user) {
@ -135,6 +139,9 @@ export async function checkPaymentStatus(eventId: string, paymentHash: string):
export async function fetchUserTickets(userId: string): Promise<Ticket[]> { export async function fetchUserTickets(userId: string): Promise<Ticket[]> {
try { try {
// Get injected LnbitsAPI service
const lnbitsAPI = injectService(SERVICE_TOKENS.LNBITS_API) as LnbitsAPI
const response = await fetch( const response = await fetch(
`${API_BASE_URL}/events/api/v1/tickets/user/${userId}`, `${API_BASE_URL}/events/api/v1/tickets/user/${userId}`,
{ {

View file

@ -61,16 +61,33 @@ interface User {
} }
} }
import { BaseService } from '@/core/base/BaseService'
import { getApiUrl, getAuthToken, setAuthToken, removeAuthToken } from '@/lib/config/lnbits' 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 private accessToken: string | null = null
constructor() { constructor() {
super()
// Try to load token from localStorage // Try to load token from localStorage
this.accessToken = getAuthToken() this.accessToken = getAuthToken()
} }
/**
* Service-specific initialization (called by BaseService)
*/
protected async onInitialize(): Promise<void> {
this.debug('LnbitsAPI initialized')
// Service is ready to use
}
private async request<T>( private async request<T>(
endpoint: string, endpoint: string,
options: RequestInit = {} 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 } export type { LoginCredentials, RegisterData, AuthResponse, User }

View file

@ -2,14 +2,14 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { BaseService } from '@/core/base/BaseService' import { BaseService } from '@/core/base/BaseService'
import { eventBus } from '@/core/event-bus' 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 { export class AuthService extends BaseService {
// Service metadata // Service metadata
protected readonly metadata = { protected readonly metadata = {
name: 'AuthService', name: 'AuthService',
version: '1.0.0', version: '1.0.0',
dependencies: [] // Auth service has no dependencies on other services dependencies: ['LnbitsAPI'] // Auth service depends on LnbitsAPI for authentication
} }
// Public state // Public state
@ -47,7 +47,7 @@ export class AuthService extends BaseService {
} }
async checkAuth(): Promise<boolean> { async checkAuth(): Promise<boolean> {
if (!lnbitsAPI.isAuthenticated()) { if (!this.lnbitsAPI.isAuthenticated()) {
this.debug('No auth token found - user needs to login') this.debug('No auth token found - user needs to login')
this.isAuthenticated.value = false this.isAuthenticated.value = false
this.user.value = null this.user.value = null
@ -56,7 +56,7 @@ export class AuthService extends BaseService {
try { try {
this.isLoading.value = true this.isLoading.value = true
const userData = await lnbitsAPI.getCurrentUser() const userData = await this.lnbitsAPI.getCurrentUser()
this.user.value = userData this.user.value = userData
this.isAuthenticated.value = true this.isAuthenticated.value = true
@ -70,7 +70,7 @@ export class AuthService extends BaseService {
this.isAuthenticated.value = false this.isAuthenticated.value = false
this.user.value = null this.user.value = null
// Clear invalid token // Clear invalid token
lnbitsAPI.logout() this.lnbitsAPI.logout()
return false return false
} finally { } finally {
this.isLoading.value = false this.isLoading.value = false
@ -81,8 +81,8 @@ export class AuthService extends BaseService {
this.isLoading.value = true this.isLoading.value = true
try { try {
await lnbitsAPI.login(credentials) await this.lnbitsAPI.login(credentials)
const userData = await lnbitsAPI.getCurrentUser() const userData = await this.lnbitsAPI.getCurrentUser()
this.user.value = userData this.user.value = userData
this.isAuthenticated.value = true this.isAuthenticated.value = true
@ -102,8 +102,8 @@ export class AuthService extends BaseService {
this.isLoading.value = true this.isLoading.value = true
try { try {
await lnbitsAPI.register(data) await this.lnbitsAPI.register(data)
const userData = await lnbitsAPI.getCurrentUser() const userData = await this.lnbitsAPI.getCurrentUser()
this.user.value = userData this.user.value = userData
this.isAuthenticated.value = true this.isAuthenticated.value = true
@ -120,7 +120,7 @@ export class AuthService extends BaseService {
} }
async logout(): Promise<void> { async logout(): Promise<void> {
lnbitsAPI.logout() this.lnbitsAPI.logout()
this.user.value = null this.user.value = null
this.isAuthenticated.value = false this.isAuthenticated.value = false
this.error.value = null this.error.value = null
@ -134,14 +134,14 @@ export class AuthService extends BaseService {
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {
// Alias for checkAuth for compatibility // Call BaseService initialize first to inject dependencies
await this.checkAuth() await super.initialize()
} }
async updatePassword(currentPassword: string, newPassword: string): Promise<void> { async updatePassword(currentPassword: string, newPassword: string): Promise<void> {
try { try {
this.isLoading.value = true this.isLoading.value = true
const updatedUser = await lnbitsAPI.updatePassword(currentPassword, newPassword) const updatedUser = await this.lnbitsAPI.updatePassword(currentPassword, newPassword)
this.user.value = updatedUser this.user.value = updatedUser
} catch (error) { } catch (error) {
const err = this.handleError(error, 'updatePassword') const err = this.handleError(error, 'updatePassword')
@ -154,7 +154,7 @@ export class AuthService extends BaseService {
async updateProfile(data: Partial<User>): Promise<void> { async updateProfile(data: Partial<User>): Promise<void> {
try { try {
this.isLoading.value = true this.isLoading.value = true
const updatedUser = await lnbitsAPI.updateProfile(data) const updatedUser = await this.lnbitsAPI.updateProfile(data)
this.user.value = updatedUser this.user.value = updatedUser
} catch (error) { } catch (error) {
const err = this.handleError(error, 'updateProfile') const err = this.handleError(error, 'updateProfile')

View file

@ -15,9 +15,11 @@ import { visibilityService } from '@/core/services/VisibilityService'
import { storageService } from '@/core/services/StorageService' import { storageService } from '@/core/services/StorageService'
import { toastService } from '@/core/services/ToastService' import { toastService } from '@/core/services/ToastService'
import { InvoiceService } from '@/core/services/invoiceService' import { InvoiceService } from '@/core/services/invoiceService'
import { LnbitsAPI } from '@/lib/api/lnbits'
// Create service instances // Create service instances
const invoiceService = new InvoiceService() const invoiceService = new InvoiceService()
const lnbitsAPI = new LnbitsAPI()
/** /**
* Base Module Plugin * Base Module Plugin
@ -51,12 +53,22 @@ export const baseModule: ModulePlugin = {
// Register invoice service // Register invoice service
container.provide(SERVICE_TOKENS.INVOICE_SERVICE, invoiceService) container.provide(SERVICE_TOKENS.INVOICE_SERVICE, invoiceService)
// Register API services
container.provide(SERVICE_TOKENS.LNBITS_API, lnbitsAPI)
// Register PWA service // Register PWA service
container.provide('pwaService', pwaService) container.provide('pwaService', pwaService)
// Initialize core services // Initialize core services in dependency order
relayHub.setRelayUrls(options?.config?.nostr?.relays || []) relayHub.setRelayUrls(options?.config?.nostr?.relays || [])
await relayHub.initialize() 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 // Auth initialization moved to app.ts before router guards
await paymentService.initialize({ await paymentService.initialize({
waitForDependencies: true, // PaymentService depends on AuthService waitForDependencies: true, // PaymentService depends on AuthService
@ -74,6 +86,7 @@ export const baseModule: ModulePlugin = {
waitForDependencies: false, // ToastService has no dependencies waitForDependencies: false, // ToastService has no dependencies
maxRetries: 1 maxRetries: 1
}) })
// InvoiceService doesn't need initialization as it's not a BaseService
console.log('✅ Base module installed successfully') console.log('✅ Base module installed successfully')
}, },
@ -88,6 +101,12 @@ export const baseModule: ModulePlugin = {
await visibilityService.dispose() await visibilityService.dispose()
await storageService.dispose() await storageService.dispose()
await toastService.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') console.log('✅ Base module uninstalled')
}, },