mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
feat: add top-artists report
This commit is contained in:
parent
6a6ba493f6
commit
6fc10c40ca
18 changed files with 345 additions and 30 deletions
18
src/reports/dto/get-top-artists-report.dto.ts
Normal file
18
src/reports/dto/get-top-artists-report.dto.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { IsEnum, IsISO8601, ValidateIf } from "class-validator";
|
||||
import { User } from "../../users/user.entity";
|
||||
import { TimePreset } from "../timePreset.enum";
|
||||
|
||||
export class GetTopArtistsReportDto {
|
||||
user: User;
|
||||
|
||||
@IsEnum(TimePreset)
|
||||
timePreset: TimePreset;
|
||||
|
||||
@ValidateIf((o) => o.timePreset === TimePreset.CUSTOM)
|
||||
@IsISO8601()
|
||||
customTimeStart: string;
|
||||
|
||||
@ValidateIf((o) => o.timePreset === TimePreset.CUSTOM)
|
||||
@IsISO8601()
|
||||
customTimeEnd: string;
|
||||
}
|
||||
8
src/reports/dto/top-artists-report.dto.ts
Normal file
8
src/reports/dto/top-artists-report.dto.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Artist } from "../../music-library/artist.entity";
|
||||
|
||||
export class TopArtistsReportDto {
|
||||
items: {
|
||||
artist: Artist;
|
||||
count: number;
|
||||
}[];
|
||||
}
|
||||
|
|
@ -3,7 +3,9 @@ import { Auth } from "src/auth/decorators/auth.decorator";
|
|||
import { ReqUser } from "../auth/decorators/req-user.decorator";
|
||||
import { User } from "../users/user.entity";
|
||||
import { GetListenReportDto } from "./dto/get-listen-report.dto";
|
||||
import { GetTopArtistsReportDto } from "./dto/get-top-artists-report.dto";
|
||||
import { ListenReportDto } from "./dto/listen-report.dto";
|
||||
import { TopArtistsReportDto } from "./dto/top-artists-report.dto";
|
||||
import { ReportsService } from "./reports.service";
|
||||
|
||||
@Controller("api/v1/reports")
|
||||
|
|
@ -18,4 +20,13 @@ export class ReportsController {
|
|||
): Promise<ListenReportDto> {
|
||||
return this.reportsService.getListens({ ...options, user });
|
||||
}
|
||||
|
||||
@Get("top-artists")
|
||||
@Auth()
|
||||
async getTopArtists(
|
||||
@Query() options: Omit<GetTopArtistsReportDto, "user">,
|
||||
@ReqUser() user: User
|
||||
): Promise<TopArtistsReportDto> {
|
||||
return this.reportsService.getTopArtists({ ...options, user });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ import {
|
|||
} from "date-fns";
|
||||
import { ListensService } from "../listens/listens.service";
|
||||
import { GetListenReportDto } from "./dto/get-listen-report.dto";
|
||||
import { GetTopArtistsReportDto } from "./dto/get-top-artists-report.dto";
|
||||
import { ListenReportDto } from "./dto/listen-report.dto";
|
||||
import { TopArtistsReportDto } from "./dto/top-artists-report.dto";
|
||||
import { Interval } from "./interval";
|
||||
import { Timeframe } from "./timeframe.enum";
|
||||
import { TimePreset } from "./timePreset.enum";
|
||||
|
||||
|
|
@ -55,6 +58,8 @@ const timePresetToDays: { [x in TimePreset]: number } = {
|
|||
[TimePreset.CUSTOM]: 0, // Not used for this
|
||||
};
|
||||
|
||||
const PAGINATION_LIMIT_UNLIMITED = 10000000;
|
||||
|
||||
@Injectable()
|
||||
export class ReportsService {
|
||||
constructor(private readonly listensService: ListensService) {}
|
||||
|
|
@ -73,7 +78,7 @@ export class ReportsService {
|
|||
user,
|
||||
filter: { time: interval },
|
||||
page: 1,
|
||||
limit: 10000000,
|
||||
limit: PAGINATION_LIMIT_UNLIMITED,
|
||||
});
|
||||
|
||||
const reportInterval: Interval = {
|
||||
|
|
@ -91,6 +96,54 @@ export class ReportsService {
|
|||
|
||||
return { items: reportItems, timeStart, timeEnd, timeFrame };
|
||||
}
|
||||
|
||||
async getTopArtists(
|
||||
options: GetTopArtistsReportDto
|
||||
): Promise<TopArtistsReportDto> {
|
||||
const { user, timePreset, customTimeStart, customTimeEnd } = options;
|
||||
|
||||
const interval = this.getIntervalFromPreset({
|
||||
timePreset,
|
||||
customTimeStart,
|
||||
customTimeEnd,
|
||||
});
|
||||
|
||||
const { items: listens } = await this.listensService.getListens({
|
||||
user,
|
||||
filter: { time: interval },
|
||||
page: 1,
|
||||
limit: PAGINATION_LIMIT_UNLIMITED,
|
||||
});
|
||||
|
||||
// Declare types for metrics calculation
|
||||
type Item = TopArtistsReportDto["items"][0];
|
||||
type Accumulator = {
|
||||
[x: string]: Item;
|
||||
};
|
||||
|
||||
const items: TopArtistsReportDto["items"] = Object.values<Item>(
|
||||
listens
|
||||
.flatMap((listen) => listen.track.artists)
|
||||
.reduce<Accumulator>((counters, artist) => {
|
||||
if (!counters[artist.id]) {
|
||||
counters[artist.id] = {
|
||||
artist,
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
|
||||
counters[artist.id].count += 1;
|
||||
|
||||
return counters;
|
||||
}, {})
|
||||
)
|
||||
.sort((a, b) => a.count - b.count)
|
||||
.reverse() // sort descending
|
||||
.slice(0, 20); // TODO: Make configurable
|
||||
|
||||
return { items };
|
||||
}
|
||||
|
||||
private getIntervalFromPreset(options: {
|
||||
timePreset: TimePreset;
|
||||
customTimeStart?: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue