refactor: update caching and zap handling in components
- Replaced CacheInterceptor with HttpCacheInterceptor in app.module.ts for improved caching strategy. - Enhanced ContentDetailModal to dispatch a custom event upon zap completion, improving inter-component communication. - Refactored ContentRow to streamline zap stats fetching and added a listener for zap completion events, ensuring real-time updates. - Updated Analytics.vue to improve number formatting functions, handling undefined and null values more robustly. These changes enhance the application's performance and user experience by optimizing caching and ensuring accurate, real-time data updates across components.
This commit is contained in:
@@ -17,7 +17,8 @@ import { SubscriptionsModule } from './subscriptions/subscriptions.module';
|
|||||||
import { AwardIssuersModule } from './award-issuers/award-issuers.module';
|
import { AwardIssuersModule } from './award-issuers/award-issuers.module';
|
||||||
import { FestivalsModule } from './festivals/festivals.module';
|
import { FestivalsModule } from './festivals/festivals.module';
|
||||||
import { GenresModule } from './genres/genres.module';
|
import { GenresModule } from './genres/genres.module';
|
||||||
import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager';
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
|
import { HttpCacheInterceptor } from './common/interceptors/http-cache.interceptor';
|
||||||
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
import { RentsModule } from './rents/rents.module';
|
import { RentsModule } from './rents/rents.module';
|
||||||
import { EventsModule } from './events/events.module';
|
import { EventsModule } from './events/events.module';
|
||||||
@@ -102,7 +103,7 @@ import { ZapsModule } from './zaps/zaps.module';
|
|||||||
AppService,
|
AppService,
|
||||||
{
|
{
|
||||||
provide: APP_INTERCEPTOR,
|
provide: APP_INTERCEPTOR,
|
||||||
useClass: CacheInterceptor,
|
useClass: HttpCacheInterceptor,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: APP_FILTER,
|
provide: APP_FILTER,
|
||||||
|
|||||||
18
backend/src/common/interceptors/http-cache.interceptor.ts
Normal file
18
backend/src/common/interceptors/http-cache.interceptor.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { CacheInterceptor } from '@nestjs/cache-manager';
|
||||||
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global cache interceptor that skips caching for paths that must stay fresh
|
||||||
|
* (e.g. /zaps/stats so zap counts update after a user zaps).
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class HttpCacheInterceptor extends CacheInterceptor {
|
||||||
|
trackBy(context: ExecutionContext): string | undefined {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const path = request.url?.split('?')[0] ?? '';
|
||||||
|
if (path.startsWith('/zaps') || path.startsWith('/api/zaps')) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return super.trackBy(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -543,7 +543,10 @@ function handleZap() {
|
|||||||
|
|
||||||
function handleZapped(_amount: number) {
|
function handleZapped(_amount: number) {
|
||||||
// In-app zaps go through BTCPay; refetch backend stats so modal updates.
|
// In-app zaps go through BTCPay; refetch backend stats so modal updates.
|
||||||
if (props.content?.id) fetchBackendZapStats(props.content.id)
|
if (props.content?.id) {
|
||||||
|
fetchBackendZapStats(props.content.id)
|
||||||
|
window.dispatchEvent(new CustomEvent('indeehub:zap-completed', { detail: { contentId: props.content.id } }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getZapperName(pubkey: string): string {
|
function getZapperName(pubkey: string): string {
|
||||||
|
|||||||
@@ -95,25 +95,27 @@ const { getStats } = useContentDiscovery()
|
|||||||
/** Backend zap stats (BTCPay zaps) so film cards show total + who zapped. */
|
/** Backend zap stats (BTCPay zaps) so film cards show total + who zapped. */
|
||||||
const backendZapStats = ref<Record<string, { zapCount: number; zapAmountSats: number; recentZapperPubkeys: string[] }>>({})
|
const backendZapStats = ref<Record<string, { zapCount: number; zapAmountSats: number; recentZapperPubkeys: string[] }>>({})
|
||||||
|
|
||||||
watch(
|
function fetchZapStats() {
|
||||||
() => props.contents,
|
const ids = props.contents?.map((c) => c.id).filter(Boolean) ?? []
|
||||||
(contents) => {
|
if (ids.length === 0) {
|
||||||
const ids = contents?.map((c) => c.id).filter(Boolean) ?? []
|
backendZapStats.value = {}
|
||||||
if (ids.length === 0) {
|
return
|
||||||
|
}
|
||||||
|
indeehubApiService
|
||||||
|
.getZapStats(ids)
|
||||||
|
.then((data) => {
|
||||||
|
backendZapStats.value = data
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
backendZapStats.value = {}
|
backendZapStats.value = {}
|
||||||
return
|
})
|
||||||
}
|
}
|
||||||
indeehubApiService
|
|
||||||
.getZapStats(ids)
|
watch(() => props.contents, fetchZapStats, { immediate: true })
|
||||||
.then((data) => {
|
|
||||||
backendZapStats.value = data
|
function onZapCompleted() {
|
||||||
})
|
fetchZapStats()
|
||||||
.catch(() => {
|
}
|
||||||
backendZapStats.value = {}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
function getReactionCount(contentId: string): number {
|
function getReactionCount(contentId: string): number {
|
||||||
return getStats(contentId).plusCount ?? 0
|
return getStats(contentId).plusCount ?? 0
|
||||||
@@ -156,12 +158,14 @@ onMounted(() => {
|
|||||||
sliderRef.value.addEventListener('scroll', handleScroll)
|
sliderRef.value.addEventListener('scroll', handleScroll)
|
||||||
handleScroll()
|
handleScroll()
|
||||||
}
|
}
|
||||||
|
window.addEventListener('indeehub:zap-completed', onZapCompleted)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (sliderRef.value) {
|
if (sliderRef.value) {
|
||||||
sliderRef.value.removeEventListener('scroll', handleScroll)
|
sliderRef.value.removeEventListener('scroll', handleScroll)
|
||||||
}
|
}
|
||||||
|
window.removeEventListener('indeehub:zap-completed', onZapCompleted)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -243,23 +243,28 @@ const rentalPct = computed(() =>
|
|||||||
totalRevenue.value > 0 ? Math.round(((watchAnalytics.value?.rentalRevenueSats || 0) / totalRevenue.value) * 100) : 50
|
totalRevenue.value > 0 ? Math.round(((watchAnalytics.value?.rentalRevenueSats || 0) / totalRevenue.value) * 100) : 50
|
||||||
)
|
)
|
||||||
|
|
||||||
function formatNumber(n: number): string {
|
function formatNumber(n: number | undefined | null): string {
|
||||||
return n.toLocaleString()
|
const val = n ?? 0
|
||||||
|
return typeof val === 'number' && !Number.isNaN(val) ? val.toLocaleString() : '0'
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSats(n: number): string {
|
function formatSats(n: number | undefined | null): string {
|
||||||
return n.toLocaleString()
|
const val = n ?? 0
|
||||||
|
return typeof val === 'number' && !Number.isNaN(val) ? val.toLocaleString() : '0'
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDuration(seconds: number): string {
|
function formatDuration(seconds: number | undefined | null): string {
|
||||||
if (!seconds) return '0m'
|
const s = seconds ?? 0
|
||||||
const m = Math.floor(seconds / 60)
|
if (!s || typeof s !== 'number' || Number.isNaN(s)) return '0m'
|
||||||
const s = Math.round(seconds % 60)
|
const m = Math.floor(s / 60)
|
||||||
return m > 0 ? `${m}m ${s}s` : `${s}s`
|
const sec = Math.round(s % 60)
|
||||||
|
return m > 0 ? `${m}m ${sec}s` : `${sec}s`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(dateString: string): string {
|
function formatDate(dateString: string | undefined | null): string {
|
||||||
return new Date(dateString).toLocaleDateString('en-US', {
|
if (dateString == null || dateString === '') return '—'
|
||||||
|
const date = new Date(dateString)
|
||||||
|
return Number.isNaN(date.getTime()) ? '—' : date.toLocaleDateString('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
|
|||||||
Reference in New Issue
Block a user