feat: enhance webhook event handling and improve profile fetching logic
- Updated the WebhooksService to differentiate between 'InvoiceSettled' and 'InvoicePaymentSettled' events, ensuring accurate payment confirmation and logging. - Enhanced the useAccounts composable to improve display name handling by skipping generic placeholder names and ensuring the most recent profile metadata is fetched from multiple relays. - Modified the IndeehubApiService to handle JWT token refresh failures gracefully, allowing public endpoints to function without authentication. - Updated content store logic to fetch all published projects from the public API, ensuring backstage content is visible to all users regardless of their active content source. These changes improve the reliability of payment processing, enhance user profile representation, and ensure content visibility across the application.
This commit is contained in:
@@ -7,7 +7,8 @@ import { contentService } from '../services/content.service'
|
||||
import { mapApiProjectsToContents } from '../utils/mappers'
|
||||
import { useContentSourceStore } from './contentSource'
|
||||
import { indeehubApiService } from '../services/indeehub-api.service'
|
||||
import { useFilmmaker } from '../composables/useFilmmaker'
|
||||
// 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', () => {
|
||||
@@ -73,7 +74,7 @@ export const useContentStore = defineStore('content', () => {
|
||||
*/
|
||||
async function fetchContentFromIndeehubApi() {
|
||||
try {
|
||||
const response = await indeehubApiService.getProjects()
|
||||
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 ?? []
|
||||
@@ -181,77 +182,67 @@ export const useContentStore = defineStore('content', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert published filmmaker projects to Content format and merge
|
||||
* them into the existing content rows so they appear on the browse page.
|
||||
* 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.
|
||||
*
|
||||
* If the filmmaker projects haven't been loaded yet (e.g. first visit
|
||||
* is the homepage, not backstage), attempt to fetch them first — but
|
||||
* only when the user has an active session.
|
||||
* 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 {
|
||||
const filmmaker = useFilmmaker()
|
||||
// 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
|
||||
|
||||
// If projects aren't loaded yet, check whether the user has
|
||||
// session tokens. We check sessionStorage directly because
|
||||
// authStore.initialize() may still be in-flight on first load.
|
||||
if (filmmaker.projects.value.length === 0) {
|
||||
const nostrToken = sessionStorage.getItem('nostr_token')
|
||||
const cognitoToken = sessionStorage.getItem('auth_token')
|
||||
const hasSession = !!nostrToken || !!cognitoToken
|
||||
|
||||
if (hasSession) {
|
||||
// Always sync nip98Service tokens on page load. The token
|
||||
// may exist in sessionStorage but its expiry record may be
|
||||
// stale, causing nip98Service.accessToken to return null.
|
||||
// Re-storing refreshes the expiry so the interceptor can
|
||||
// include the token; if it really expired the backend 401
|
||||
// interceptor will auto-refresh via the refresh token.
|
||||
if (nostrToken) {
|
||||
const { nip98Service } = await import('../services/nip98.service')
|
||||
nip98Service.storeTokens(
|
||||
nostrToken,
|
||||
sessionStorage.getItem('indeehub_api_refresh') ?? sessionStorage.getItem('refresh_token') ?? '',
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
await filmmaker.fetchProjects()
|
||||
} catch (err) {
|
||||
console.warn('[content-store] Failed to fetch filmmaker projects for merge:', err)
|
||||
}
|
||||
}
|
||||
// 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 published = filmmaker.projects.value.filter(p => p.status === 'published')
|
||||
if (published.length === 0) return
|
||||
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 published projects:',
|
||||
published.map(p => ({ id: p.id, title: p.title, filmId: p.film?.id, rentalPrice: p.film?.rentalPrice ?? p.rentalPrice })),
|
||||
'[content-store] Merging API published projects:',
|
||||
publishedContent.map(c => ({ id: c.id, title: c.title, isOwn: c.isOwnProject })),
|
||||
)
|
||||
|
||||
const publishedContent: Content[] = published.map(p => ({
|
||||
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 => g.name) || [],
|
||||
slug: p.slug,
|
||||
rentalPrice: p.film?.rentalPrice ?? p.rentalPrice,
|
||||
status: p.status,
|
||||
apiData: p,
|
||||
isOwnProject: true,
|
||||
}))
|
||||
|
||||
// Merge into each content row (prepend so they appear first)
|
||||
for (const key of Object.keys(contentRows.value)) {
|
||||
// Avoid duplicates by filtering out any already-present IDs
|
||||
const existingIds = new Set(contentRows.value[key].map(c => c.id))
|
||||
const newItems = publishedContent.filter(c => !existingIds.has(c.id))
|
||||
if (newItems.length > 0) {
|
||||
@@ -263,8 +254,8 @@ export const useContentStore = defineStore('content', () => {
|
||||
if (!featuredContent.value && publishedContent.length > 0) {
|
||||
featuredContent.value = publishedContent[0]
|
||||
}
|
||||
} catch {
|
||||
// Filmmaker composable may not be initialized yet — safe to ignore
|
||||
} catch (err) {
|
||||
console.warn('[content-store] Failed to merge API published projects:', err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user