Implement modular architecture with core services and Nostr integration
- Introduce a modular application structure with a new app configuration file to manage module settings and features. - Implement a dependency injection container for service management across modules. - Create a plugin manager to handle module registration, installation, and lifecycle management. - Develop a global event bus for inter-module communication, enhancing loose coupling between components. - Add core modules including base functionalities, Nostr feed, and PWA services, with support for dynamic loading and configuration. - Establish a Nostr client hub for managing WebSocket connections and event handling. - Enhance user experience with a responsive Nostr feed component, integrating admin announcements and community posts. - Refactor existing components to align with the new modular architecture, improving maintainability and scalability.
This commit is contained in:
parent
2d8215a35e
commit
519a9003d4
16 changed files with 2520 additions and 14 deletions
130
src/modules/nostr-feed/composables/useFeed.ts
Normal file
130
src/modules/nostr-feed/composables/useFeed.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||
import { eventBus } from '@/core/event-bus'
|
||||
import type { Event as NostrEvent, Filter } from 'nostr-tools'
|
||||
|
||||
export interface FeedConfig {
|
||||
feedType: 'announcements' | 'general' | 'mentions'
|
||||
maxPosts?: number
|
||||
refreshInterval?: number
|
||||
adminPubkeys?: string[]
|
||||
}
|
||||
|
||||
export function useFeed(config: FeedConfig) {
|
||||
const relayHub = injectService<any>(SERVICE_TOKENS.RELAY_HUB)
|
||||
const posts = ref<NostrEvent[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
let refreshTimer: number | null = null
|
||||
let unsubscribe: (() => void) | null = null
|
||||
|
||||
const filteredPosts = computed(() => {
|
||||
let filtered = posts.value
|
||||
|
||||
// Filter by feed type
|
||||
if (config.feedType === 'announcements' && config.adminPubkeys) {
|
||||
filtered = filtered.filter(post => config.adminPubkeys!.includes(post.pubkey))
|
||||
}
|
||||
|
||||
// Sort by created timestamp (newest first)
|
||||
filtered = filtered.sort((a, b) => b.created_at - a.created_at)
|
||||
|
||||
// Limit posts
|
||||
if (config.maxPosts) {
|
||||
filtered = filtered.slice(0, config.maxPosts)
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
const loadFeed = async () => {
|
||||
if (!relayHub) {
|
||||
error.value = 'RelayHub not available'
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
// Create filter based on feed type
|
||||
const filter: Filter = {
|
||||
kinds: [1], // Text notes
|
||||
limit: config.maxPosts || 50
|
||||
}
|
||||
|
||||
if (config.feedType === 'announcements' && config.adminPubkeys) {
|
||||
filter.authors = config.adminPubkeys
|
||||
}
|
||||
|
||||
// Subscribe to events
|
||||
await relayHub.subscribe('feed-subscription', [filter], {
|
||||
onEvent: (event: NostrEvent) => {
|
||||
// Add new event if not already present
|
||||
if (!posts.value.some(p => p.id === event.id)) {
|
||||
posts.value = [event, ...posts.value]
|
||||
|
||||
// Emit event for other modules
|
||||
eventBus.emit('nostr-feed:new-post', { event, feedType: config.feedType }, 'nostr-feed')
|
||||
}
|
||||
},
|
||||
onEose: () => {
|
||||
console.log('Feed subscription end of stored events')
|
||||
isLoading.value = false
|
||||
},
|
||||
onClose: () => {
|
||||
console.log('Feed subscription closed')
|
||||
}
|
||||
})
|
||||
|
||||
unsubscribe = () => {
|
||||
relayHub.unsubscribe('feed-subscription')
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to load feed:', err)
|
||||
error.value = err instanceof Error ? err.message : 'Failed to load feed'
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshFeed = () => {
|
||||
posts.value = []
|
||||
loadFeed()
|
||||
}
|
||||
|
||||
const startAutoRefresh = () => {
|
||||
if (config.refreshInterval && config.refreshInterval > 0) {
|
||||
refreshTimer = setInterval(refreshFeed, config.refreshInterval) as unknown as number
|
||||
}
|
||||
}
|
||||
|
||||
const stopAutoRefresh = () => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadFeed()
|
||||
startAutoRefresh()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopAutoRefresh()
|
||||
if (unsubscribe) {
|
||||
unsubscribe()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
posts: filteredPosts,
|
||||
isLoading,
|
||||
error,
|
||||
refreshFeed,
|
||||
loadFeed
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue