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:
Dorian
2026-02-14 12:05:32 +00:00
parent bbac44854c
commit d1ac281ad9
6 changed files with 150 additions and 87 deletions

View File

@@ -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)
}
}