diff --git a/src/app.config.ts b/src/app.config.ts index 09c9625..bd7190d 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -36,7 +36,10 @@ export const appConfig: AppConfig = { config: { defaultCurrency: 'sats', paymentTimeout: 300000, // 5 minutes - maxOrderHistory: 50 + maxOrderHistory: 50, + apiConfig: { + baseUrl: import.meta.env.VITE_LNBITS_BASE_URL || 'http://localhost:5000' + } } }, chat: { diff --git a/src/core/di-container.ts b/src/core/di-container.ts index 7e494d2..e24ebd1 100644 --- a/src/core/di-container.ts +++ b/src/core/di-container.ts @@ -140,6 +140,7 @@ export const SERVICE_TOKENS = { // Nostrmarket services NOSTRMARKET_SERVICE: Symbol('nostrmarketService'), + NOSTRMARKET_API: Symbol('nostrmarketAPI'), // API services LNBITS_API: Symbol('lnbitsAPI'), diff --git a/src/modules/market/components/MerchantStore.vue b/src/modules/market/components/MerchantStore.vue index 6652331..4b07062 100644 --- a/src/modules/market/components/MerchantStore.vue +++ b/src/modules/market/components/MerchantStore.vue @@ -1,7 +1,28 @@ diff --git a/src/modules/market/index.ts b/src/modules/market/index.ts index e18c26a..24bf032 100644 --- a/src/modules/market/index.ts +++ b/src/modules/market/index.ts @@ -20,6 +20,7 @@ import { useMarketPreloader } from './composables/useMarketPreloader' // Import services import { NostrmarketService } from './services/nostrmarketService' import { PaymentMonitorService } from './services/paymentMonitor' +import { NostrmarketAPI } from './services/nostrmarketAPI' export interface MarketModuleConfig { defaultCurrency: string @@ -49,6 +50,9 @@ export const marketModule: ModulePlugin = { const nostrmarketService = new NostrmarketService() container.provide(SERVICE_TOKENS.NOSTRMARKET_SERVICE, nostrmarketService) + const nostrmarketAPI = new NostrmarketAPI() + container.provide(SERVICE_TOKENS.NOSTRMARKET_API, nostrmarketAPI) + const paymentMonitorService = new PaymentMonitorService() container.provide(SERVICE_TOKENS.PAYMENT_MONITOR, paymentMonitorService) @@ -61,6 +65,14 @@ export const marketModule: ModulePlugin = { // Service will auto-initialize when dependencies are available }) + await nostrmarketAPI.initialize({ + waitForDependencies: true, + maxRetries: 3 + }).catch(error => { + console.warn('🛒 NostrmarketAPI initialization deferred:', error) + // Service will auto-initialize when dependencies are available + }) + await paymentMonitorService.initialize({ waitForDependencies: true, maxRetries: 3 @@ -87,6 +99,7 @@ export const marketModule: ModulePlugin = { // Clean up services container.remove(SERVICE_TOKENS.NOSTRMARKET_SERVICE) + container.remove(SERVICE_TOKENS.NOSTRMARKET_API) container.remove(SERVICE_TOKENS.PAYMENT_MONITOR) console.log('✅ Market module uninstalled') diff --git a/src/modules/market/services/nostrmarketAPI.ts b/src/modules/market/services/nostrmarketAPI.ts new file mode 100644 index 0000000..2f584ab --- /dev/null +++ b/src/modules/market/services/nostrmarketAPI.ts @@ -0,0 +1,221 @@ +import { BaseService } from '@/core/base/BaseService' +import appConfig from '@/app.config' + +export interface Merchant { + id: string + private_key: string + public_key: string + time?: number + config: { + name?: string + about?: string + picture?: string + event_id?: string + sync_from_nostr?: boolean + active: boolean + restore_in_progress?: boolean + } +} + +export interface Stall { + id: string + wallet: string + name: string + currency: string + shipping_zones: Array<{ + id: string + name: string + cost: number + countries: string[] + }> + config: { + image_url?: string + description?: string + } + pending: boolean + event_id?: string + event_created_at?: number +} + +export interface CreateMerchantRequest { + config: { + name?: string + about?: string + picture?: string + sync_from_nostr?: boolean + active?: boolean + } +} + +export interface CreateStallRequest { + wallet: string + name: string + currency: string + shipping_zones: Array<{ + id: string + name: string + cost: number + countries: string[] + }> + config: { + image_url?: string + description?: string + } +} + +export class NostrmarketAPI extends BaseService { + // Service metadata + protected readonly metadata = { + name: 'NostrmarketAPI', + version: '1.0.0', + dependencies: [] // No dependencies - this is a market-specific service + } + + private baseUrl: string + + constructor() { + super() + const config = appConfig.modules.market.config + if (!config?.apiConfig?.baseUrl) { + throw new Error('NostrmarketAPI: Missing apiConfig.baseUrl in market module config') + } + this.baseUrl = config.apiConfig.baseUrl + } + + /** + * Service-specific initialization (called by BaseService) + */ + protected async onInitialize(): Promise { + this.debug('NostrmarketAPI initialized with base URL:', this.baseUrl) + // Service is ready to use + } + + private async request( + endpoint: string, + walletKey: string, + options: RequestInit = {} + ): Promise { + const url = `${this.baseUrl}/nostrmarket${endpoint}` + + this.debug('NostrmarketAPI request:', { endpoint, fullUrl: url }) + + const headers: HeadersInit = { + 'Content-Type': 'application/json', + 'X-API-KEY': walletKey, + ...options.headers, + } + + const response = await fetch(url, { + ...options, + headers, + }) + + if (!response.ok) { + const errorText = await response.text() + this.debug('NostrmarketAPI Error:', { + status: response.status, + statusText: response.statusText, + errorText + }) + + // If 404, it means no merchant profile exists + if (response.status === 404) { + return null as T + } + + throw new Error(`NostrmarketAPI request failed: ${response.status} ${response.statusText}`) + } + + const data = await response.json() + return data + } + + /** + * Get merchant profile for the current user + * Uses wallet invoice key (inkey) as per the API specification + */ + async getMerchant(walletInkey: string): Promise { + try { + const merchant = await this.request( + '/api/v1/merchant', + walletInkey, + { method: 'GET' } + ) + + this.debug('Retrieved merchant:', { + exists: !!merchant, + merchantId: merchant?.id, + active: merchant?.config?.active + }) + + return merchant + } catch (error) { + this.debug('Failed to get merchant:', error) + // Return null instead of throwing - no merchant profile exists + return null + } + } + + /** + * Create a new merchant profile + * Uses wallet admin key as per the API specification + */ + async createMerchant( + walletAdminkey: string, + merchantData: CreateMerchantRequest + ): Promise { + const merchant = await this.request( + '/api/v1/merchant', + walletAdminkey, + { + method: 'POST', + body: JSON.stringify(merchantData), + } + ) + + this.debug('Created merchant:', { merchantId: merchant.id }) + + return merchant + } + + /** + * Get stalls for the current merchant + */ + async getStalls(walletInkey: string): Promise { + try { + const stalls = await this.request( + '/api/v1/stalls', + walletInkey, + { method: 'GET' } + ) + + this.debug('Retrieved stalls:', { count: stalls?.length || 0 }) + + return stalls || [] + } catch (error) { + this.debug('Failed to get stalls:', error) + return [] + } + } + + /** + * Create a new stall + */ + async createStall( + walletAdminkey: string, + stallData: CreateStallRequest + ): Promise { + const stall = await this.request( + '/api/v1/stall', + walletAdminkey, + { + method: 'POST', + body: JSON.stringify(stallData), + } + ) + + this.debug('Created stall:', { stallId: stall.id }) + + return stall + } +} \ No newline at end of file