feat(charts): сбор статистики проигрываний и API чартов
- модели Track / TrackPlay / TrackLike (+ миграция add_charts) - сбор проигрываний в now-playing-поллере: при смене трека на станции пишется TrackPlay (нормализация artist+song -> Track), fire-and-forget обогащение через MusicBrainz (album/releaseDate) - ChartsModule: GET /charts/tracks (период day/week/month/all, ранг, тренд, проигрывания, станции, лайки), GET /charts/tracks/:id (метрики, таймлайны популярности и лайков по дням, топ станций, isLiked), POST/DELETE like - OptionalAuthGuard для публичной детальной страницы с опц. userId
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { NowPlayingGateway } from './now-playing.gateway';
|
||||
import { NowPlayingService } from './now-playing.service';
|
||||
import { RecordStationSyncService } from './record-station-sync.service';
|
||||
import { IcyNowPlayingService } from './icy-now-playing.service';
|
||||
import { ChartsModule } from '../charts/charts.module';
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => ChartsModule)],
|
||||
providers: [
|
||||
NowPlayingGateway,
|
||||
NowPlayingService,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable, Logger, Inject, forwardRef } from '@nestjs/common';
|
||||
import { Interval } from '@nestjs/schedule';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { NowPlayingGateway } from './now-playing.gateway';
|
||||
import { RecordStationSyncService } from './record-station-sync.service';
|
||||
import { ChartsService } from '../charts/charts.service';
|
||||
|
||||
interface RecordTrack {
|
||||
id: number;
|
||||
@@ -26,6 +27,8 @@ export class NowPlayingService {
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly gateway: NowPlayingGateway,
|
||||
private readonly recordSync: RecordStationSyncService,
|
||||
@Inject(forwardRef(() => ChartsService))
|
||||
private readonly chartsService: ChartsService,
|
||||
) {
|
||||
this.logger.log('NowPlayingService initialized');
|
||||
}
|
||||
@@ -55,6 +58,11 @@ export class NowPlayingService {
|
||||
|
||||
const coverUrl = np.track.image600 ?? np.track.image200 ?? np.track.image100;
|
||||
|
||||
// Получаем текущее состояние до апдейта, чтобы определить смену трека
|
||||
const prev = await this.prisma.nowPlaying.findUnique({
|
||||
where: { stationId: mapping.dbId },
|
||||
});
|
||||
|
||||
const updated = await this.prisma.nowPlaying.upsert({
|
||||
where: { stationId: mapping.dbId },
|
||||
create: {
|
||||
@@ -77,6 +85,20 @@ export class NowPlayingService {
|
||||
updatedAt: updated.updatedAt,
|
||||
});
|
||||
updatedCount++;
|
||||
|
||||
// Засчитываем проигрывание только при смене трека
|
||||
const trackChanged =
|
||||
!prev ||
|
||||
prev.song !== np.track.song ||
|
||||
prev.artist !== np.track.artist;
|
||||
if (trackChanged) {
|
||||
void this.chartsService.recordPlay({
|
||||
artist: np.track.artist,
|
||||
song: np.track.song,
|
||||
coverUrl,
|
||||
stationDbId: mapping.dbId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
@@ -91,6 +113,11 @@ export class NowPlayingService {
|
||||
stationId: string,
|
||||
data: { song: string; artist: string; coverUrl?: string },
|
||||
) {
|
||||
// Получаем текущее состояние до апдейта, чтобы определить смену трека
|
||||
const prev = await this.prisma.nowPlaying.findUnique({
|
||||
where: { stationId },
|
||||
});
|
||||
|
||||
const nowPlaying = await this.prisma.nowPlaying.upsert({
|
||||
where: { stationId },
|
||||
create: {
|
||||
@@ -113,6 +140,18 @@ export class NowPlayingService {
|
||||
updatedAt: nowPlaying.updatedAt,
|
||||
});
|
||||
|
||||
// Засчитываем проигрывание только при смене трека
|
||||
const trackChanged =
|
||||
!prev || prev.song !== data.song || prev.artist !== data.artist;
|
||||
if (trackChanged) {
|
||||
void this.chartsService.recordPlay({
|
||||
artist: data.artist,
|
||||
song: data.song,
|
||||
coverUrl: data.coverUrl,
|
||||
stationDbId: stationId,
|
||||
});
|
||||
}
|
||||
|
||||
return nowPlaying;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user