import { defineStore } from 'pinia' import { ref } from 'vue' import type { Content } from '../types/content' import { indeeHubFilms, bitcoinFilms, documentaries, dramas } from '../data/indeeHubFilms' import { topDocFilms, topDocBitcoin, topDocCrypto, topDocMoney, topDocEconomics } from '../data/topDocFilms' import { contentService } from '../services/content.service' import { mapApiProjectsToContents } from '../utils/mappers' import { useContentSourceStore } from './contentSource' import { indeehubApiService } from '../services/indeehub-api.service' // useFilmmaker import removed — mergePublishedFilmmakerProjects // now fetches from the public API so all users see backstage content import { USE_MOCK as USE_MOCK_DATA } from '../utils/mock' export const useContentStore = defineStore('content', () => { const featuredContent = ref(null) const contentRows = ref<{ [key: string]: Content[] }>({ featured: [], newReleases: [], bitcoin: [], documentaries: [], dramas: [], independent: [] }) const loading = ref(false) const error = ref(null) /** * Fetch content from the original API (external IndeeHub) */ async function fetchContentFromApi() { try { const projects = await contentService.getProjects({ status: 'published' }) if (projects.length === 0) { // No published content yet — not an error, use placeholder content console.info('No published content from API, using placeholder content.') await fetchContentFromMock() return } const allContent = mapApiProjectsToContents(projects) const featuredFilm = allContent.find(c => c.type === 'film') || allContent[0] featuredContent.value = featuredFilm const films = allContent.filter(c => c.type === 'film') const bitcoinContent = allContent.filter(c => c.categories?.some(cat => cat.toLowerCase().includes('bitcoin')) ) const docs = allContent.filter(c => c.categories?.some(cat => cat.toLowerCase().includes('documentary')) ) const dramaContent = allContent.filter(c => c.categories?.some(cat => cat.toLowerCase().includes('drama')) ) contentRows.value = { featured: allContent.slice(0, 10), newReleases: films.slice(0, 8), bitcoin: bitcoinContent.length > 0 ? bitcoinContent : films.slice(0, 6), documentaries: docs.length > 0 ? docs : films.slice(0, 6), dramas: dramaContent.length > 0 ? dramaContent : films.slice(0, 6), independent: films.slice(0, 10) } } catch (err) { console.error('API fetch failed:', err) throw err } } /** * Fetch content from our self-hosted IndeeHub API */ async function fetchContentFromIndeehubApi() { try { const response = await indeehubApiService.getProjects({ fresh: true }) // Handle both array responses and wrapped responses like { data: [...] } const projects = Array.isArray(response) ? response : (response as any)?.data ?? [] if (!Array.isArray(projects) || projects.length === 0) { // No published content yet — not an error, just use mock/placeholder data console.info('No published content in backend yet, using placeholder content.') await fetchContentFromMock() return } // Map API projects to frontend Content format. // The backend returns projects with a nested `film` object (the Content entity). // We need the content ID (film.id) for the rental/payment flow. const allContent: Content[] = projects.map((p: any) => ({ id: p.id, contentId: p.film?.id, title: p.title, description: p.synopsis || '', thumbnail: p.poster || '', backdrop: p.poster || '', type: p.type || 'film', slug: p.slug, rentalPrice: p.film?.rentalPrice ?? p.rentalPrice, status: p.status, categories: p.genre ? [p.genre.name] : [], streamingUrl: p.streamingUrl || p.partnerStreamUrl || undefined, apiData: { deliveryMode: p.deliveryMode, partnerStreamUrl: p.partnerStreamUrl, partnerDashUrl: p.partnerDashUrl, partnerFairplayUrl: p.partnerFairplayUrl, partnerDrmToken: p.partnerDrmToken, }, })) const featuredFilm = allContent.find(c => c.type === 'film') || allContent[0] featuredContent.value = featuredFilm const films = allContent.filter(c => c.type === 'film') const bitcoinContent = allContent.filter(c => c.categories?.some(cat => cat.toLowerCase().includes('bitcoin')) ) const docs = allContent.filter(c => c.categories?.some(cat => cat.toLowerCase().includes('documentary')) ) const dramaContent = allContent.filter(c => c.categories?.some(cat => cat.toLowerCase().includes('drama')) ) contentRows.value = { featured: allContent.slice(0, 10), newReleases: films.slice(0, 8), bitcoin: bitcoinContent.length > 0 ? bitcoinContent : films.slice(0, 6), documentaries: docs.length > 0 ? docs : allContent.slice(0, 10), dramas: dramaContent.length > 0 ? dramaContent : films.slice(0, 6), independent: films.slice(0, 10) } } catch (err) { console.error('IndeeHub API fetch failed:', err) throw err } } /** * Fetch IndeeHub mock content (original catalog) */ function fetchIndeeHubMock() { const godBlessBitcoin = bitcoinFilms.find(f => f.title === 'God Bless Bitcoin') || bitcoinFilms[0] if (godBlessBitcoin) { featuredContent.value = { ...godBlessBitcoin, backdrop: '/images/god-bless-bitcoin-backdrop.jpg' } } else { featuredContent.value = indeeHubFilms[0] } contentRows.value = { featured: indeeHubFilms.slice(0, 10), newReleases: indeeHubFilms.slice(0, 8).reverse(), bitcoin: bitcoinFilms, documentaries: documentaries.slice(0, 10), dramas: dramas.slice(0, 10), independent: indeeHubFilms.filter(f => !f.categories.includes('Bitcoin') && !f.categories.includes('Documentary') ).slice(0, 10) } } /** * Fetch TopDocumentaryFilms mock content */ function fetchTopDocMock() { featuredContent.value = topDocFilms[0] contentRows.value = { featured: topDocFilms.slice(0, 10), newReleases: [...topDocFilms].sort((a, b) => (b.releaseYear ?? 0) - (a.releaseYear ?? 0)).slice(0, 8), bitcoin: topDocBitcoin, documentaries: topDocEconomics.slice(0, 10), dramas: topDocMoney, independent: topDocCrypto.slice(0, 10) } } /** * Fetch ALL published projects from the public API and merge them * into the existing content rows so backstage content appears for * every user, regardless of which content source is active. * * Ownership is determined by comparing the project's filmmaker ID * to the current user's filmmaker profile. Only the actual creator * sees the "my movie" flag — not other logged-in users. */ async function mergePublishedFilmmakerProjects() { const apiUrl = import.meta.env.VITE_INDEEHUB_API_URL || '' if (!apiUrl) return // No backend configured try { // Fetch ALL published projects from the public API endpoint. // This works for any user (authenticated or not) — no auth required. // Use fresh: true to bypass the backend's 5-minute cache // so newly published backstage content appears immediately. const response = await indeehubApiService.getProjects({ fresh: true }) const projects = Array.isArray(response) ? response : (response as any)?.data ?? [] if (!Array.isArray(projects) || projects.length === 0) return // Determine the current user's filmmaker ID (if any) for // ownership tagging. Import lazily to avoid circular deps. let currentFilmmakerId: string | null = null try { const { useAuthStore } = await import('./auth') const authStore = useAuthStore() currentFilmmakerId = authStore.user?.filmmaker?.id ?? null } catch { // Auth store not ready — ownership will be unknown } const publishedContent: Content[] = projects.map((p: any) => { const projectFilmmakerId = p.filmmaker?.id ?? p.filmmakerId ?? null return { id: p.id, contentId: p.film?.id, title: p.title || p.name, description: p.synopsis || '', thumbnail: p.poster || '/images/placeholder-poster.jpg', backdrop: p.poster || '/images/placeholder-poster.jpg', type: p.type === 'episodic' ? 'series' as const : 'film' as const, rating: p.format || undefined, releaseYear: p.releaseDate ? new Date(p.releaseDate).getFullYear() : new Date().getFullYear(), categories: p.genres?.map((g: any) => g.name) || [], slug: p.slug, rentalPrice: p.film?.rentalPrice ?? p.rentalPrice, status: p.status, apiData: p, // Only mark as "own project" if the current user is the actual filmmaker isOwnProject: !!(currentFilmmakerId && projectFilmmakerId && currentFilmmakerId === projectFilmmakerId), } }) console.debug( '[content-store] Merging API published projects:', publishedContent.map(c => ({ id: c.id, title: c.title, isOwn: c.isOwnProject })), ) // Merge into each content row (prepend so they appear first) for (const key of Object.keys(contentRows.value)) { const existingIds = new Set(contentRows.value[key].map(c => c.id)) const newItems = publishedContent.filter(c => !existingIds.has(c.id)) if (newItems.length > 0) { contentRows.value[key] = [...newItems, ...contentRows.value[key]] } } // If no featured content yet, use the first published project if (!featuredContent.value && publishedContent.length > 0) { featuredContent.value = publishedContent[0] } } catch (err) { console.warn('[content-store] Failed to merge API published projects:', err) } } /** * Route to the correct loader based on the active content source */ async function fetchContentFromMock() { const sourceStore = useContentSourceStore() if (sourceStore.activeSource === 'topdocfilms') { fetchTopDocMock() } else { fetchIndeeHubMock() } // Also include any projects published through the backstage await mergePublishedFilmmakerProjects() } /** * Main fetch content method. * Respects the content-source toggle: * - 'indeehub-api' → self-hosted backend API * - 'topdocfilms' → TopDoc mock catalog (YouTube documentaries) * - 'indeehub' → IndeeHub mock catalog */ async function fetchContent() { loading.value = true error.value = null try { const sourceStore = useContentSourceStore() const apiUrl = import.meta.env.VITE_INDEEHUB_API_URL || '' if (USE_MOCK_DATA) { await new Promise(resolve => setTimeout(resolve, 100)) await fetchContentFromMock() } else if (sourceStore.activeSource === 'indeehub-api' && apiUrl) { // Self-hosted backend API await fetchContentFromIndeehubApi() await mergePublishedFilmmakerProjects() } else if (sourceStore.activeSource === 'topdocfilms') { // TopDoc curated catalog (free YouTube documentaries) fetchTopDocMock() await mergePublishedFilmmakerProjects() } else if (sourceStore.activeSource === 'indeehub') { // IndeeHub mock catalog fetchIndeeHubMock() await mergePublishedFilmmakerProjects() } else if (apiUrl) { // Fallback to API if source is unknown but API is configured await fetchContentFromIndeehubApi() await mergePublishedFilmmakerProjects() } else { await fetchContentFromApi() await mergePublishedFilmmakerProjects() } } catch (e: any) { error.value = e.message || 'Failed to load content' console.error('Content fetch error:', e) // Fallback to mock data on error console.log('Falling back to mock data...') await fetchContentFromMock() } finally { loading.value = false } } return { featuredContent, contentRows, loading, error, fetchContent } })