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:
74
src/charts/charts.controller.ts
Normal file
74
src/charts/charts.controller.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Delete,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
Req,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ChartsService, ChartPeriod } from './charts.service';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { OptionalAuthGuard } from '../auth/optional-auth.guard';
|
||||
import type { Request } from 'express';
|
||||
|
||||
@ApiTags('charts')
|
||||
@Controller('charts')
|
||||
export class ChartsController {
|
||||
constructor(private readonly chartsService: ChartsService) {}
|
||||
|
||||
@Get('tracks')
|
||||
@ApiOperation({ summary: 'Чарт треков за период' })
|
||||
async getTopTracks(
|
||||
@Query('period') period: string = 'week',
|
||||
@Query('limit') limit: string = '100',
|
||||
) {
|
||||
const validPeriod: ChartPeriod =
|
||||
period === 'day' || period === 'week' || period === 'month' || period === 'all'
|
||||
? (period as ChartPeriod)
|
||||
: 'week';
|
||||
const parsedLimit = Math.min(Math.max(parseInt(limit, 10) || 100, 1), 200);
|
||||
return this.chartsService.getTopTracks(validPeriod, parsedLimit);
|
||||
}
|
||||
|
||||
@Get('tracks/:trackId')
|
||||
@UseGuards(OptionalAuthGuard)
|
||||
@ApiOperation({ summary: 'Детальная страница трека' })
|
||||
async getTrackDetail(
|
||||
@Param('trackId') trackId: string,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
const user = req['user'] as { sub: string } | undefined;
|
||||
return this.chartsService.getTrackDetail(trackId, user?.sub);
|
||||
}
|
||||
|
||||
@Post('tracks/:trackId/like')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Лайкнуть трек' })
|
||||
async likeTrack(
|
||||
@Param('trackId') trackId: string,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
const user = req['user'] as { sub: string; email: string };
|
||||
return this.chartsService.likeTrack(trackId, user.sub);
|
||||
}
|
||||
|
||||
@Delete('tracks/:trackId/like')
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Убрать лайк с трека' })
|
||||
async unlikeTrack(
|
||||
@Param('trackId') trackId: string,
|
||||
@Req() req: Request,
|
||||
) {
|
||||
const user = req['user'] as { sub: string; email: string };
|
||||
return this.chartsService.unlikeTrack(trackId, user.sub);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user