mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
feat(api): add listen report endpoint
This commit is contained in:
parent
ddcdfff89b
commit
3828b841c2
9 changed files with 179 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ import { SourcesModule } from "./sources/sources.module";
|
|||
import { UsersModule } from "./users/users.module";
|
||||
import { ConfigModule } from "./config/config.module";
|
||||
import { HealthCheckModule } from "./health-check/health-check.module";
|
||||
import { ReportsModule } from "./reports/reports.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -28,6 +29,7 @@ import { HealthCheckModule } from "./health-check/health-check.module";
|
|||
MusicLibraryModule,
|
||||
ListensModule,
|
||||
HealthCheckModule,
|
||||
ReportsModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
16
src/reports/dto/get-listen-report.dto.ts
Normal file
16
src/reports/dto/get-listen-report.dto.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { IsEnum, IsISO8601 } from "class-validator";
|
||||
import { User } from "../../users/user.entity";
|
||||
import { Timeframe } from "../timeframe.enum";
|
||||
|
||||
export class GetListenReportDto {
|
||||
user: User;
|
||||
|
||||
@IsEnum(Timeframe)
|
||||
timeFrame: Timeframe;
|
||||
|
||||
@IsISO8601()
|
||||
timeStart: string;
|
||||
|
||||
@IsISO8601()
|
||||
timeEnd: string;
|
||||
}
|
||||
14
src/reports/dto/listen-report.dto.ts
Normal file
14
src/reports/dto/listen-report.dto.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { Timeframe } from "../timeframe.enum";
|
||||
|
||||
export class ListenReportDto {
|
||||
items: {
|
||||
date: string;
|
||||
count: number;
|
||||
}[];
|
||||
|
||||
timeFrame: Timeframe;
|
||||
|
||||
timeStart: string;
|
||||
|
||||
timeEnd: string;
|
||||
}
|
||||
18
src/reports/reports.controller.spec.ts
Normal file
18
src/reports/reports.controller.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ReportsController } from './reports.controller';
|
||||
|
||||
describe('Reports Controller', () => {
|
||||
let controller: ReportsController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ReportsController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ReportsController>(ReportsController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
21
src/reports/reports.controller.ts
Normal file
21
src/reports/reports.controller.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { Controller, Get, Query } from "@nestjs/common";
|
||||
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 { ListenReportDto } from "./dto/listen-report.dto";
|
||||
import { ReportsService } from "./reports.service";
|
||||
|
||||
@Controller("api/v1/reports")
|
||||
export class ReportsController {
|
||||
constructor(private readonly reportsService: ReportsService) {}
|
||||
|
||||
@Get("listens")
|
||||
@Auth()
|
||||
async getListens(
|
||||
@Query() options: Omit<GetListenReportDto, "user">,
|
||||
@ReqUser() user: User
|
||||
): Promise<ListenReportDto> {
|
||||
return this.reportsService.getListens({ ...options, user });
|
||||
}
|
||||
}
|
||||
11
src/reports/reports.module.ts
Normal file
11
src/reports/reports.module.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { ReportsService } from "./reports.service";
|
||||
import { ReportsController } from "./reports.controller";
|
||||
import { ListensModule } from "src/listens/listens.module";
|
||||
|
||||
@Module({
|
||||
imports: [ListensModule],
|
||||
providers: [ReportsService],
|
||||
controllers: [ReportsController],
|
||||
})
|
||||
export class ReportsModule {}
|
||||
18
src/reports/reports.service.spec.ts
Normal file
18
src/reports/reports.service.spec.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ReportsService } from './reports.service';
|
||||
|
||||
describe('ReportsService', () => {
|
||||
let service: ReportsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ReportsService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ReportsService>(ReportsService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
73
src/reports/reports.service.ts
Normal file
73
src/reports/reports.service.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import {
|
||||
eachDayOfInterval,
|
||||
eachMonthOfInterval,
|
||||
eachWeekOfInterval,
|
||||
eachYearOfInterval,
|
||||
formatISO,
|
||||
Interval,
|
||||
isSameDay,
|
||||
isSameMonth,
|
||||
isSameWeek,
|
||||
isSameYear,
|
||||
parseISO,
|
||||
} from "date-fns";
|
||||
import { ListensService } from "../listens/listens.service";
|
||||
import { GetListenReportDto } from "./dto/get-listen-report.dto";
|
||||
import { ListenReportDto } from "./dto/listen-report.dto";
|
||||
import { Timeframe } from "./timeframe.enum";
|
||||
|
||||
const timeframeToDateFns: {
|
||||
[x in Timeframe]: {
|
||||
eachOfInterval: (interval: Interval) => Date[];
|
||||
isSame: (dateLeft: Date, dateRight: Date) => boolean;
|
||||
};
|
||||
} = {
|
||||
[Timeframe.Day]: {
|
||||
eachOfInterval: eachDayOfInterval,
|
||||
isSame: isSameDay,
|
||||
},
|
||||
[Timeframe.Week]: {
|
||||
eachOfInterval: eachWeekOfInterval,
|
||||
isSame: isSameWeek,
|
||||
},
|
||||
[Timeframe.Month]: {
|
||||
eachOfInterval: eachMonthOfInterval,
|
||||
isSame: isSameMonth,
|
||||
},
|
||||
[Timeframe.Year]: {
|
||||
eachOfInterval: eachYearOfInterval,
|
||||
isSame: isSameYear,
|
||||
},
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ReportsService {
|
||||
constructor(private readonly listensService: ListensService) {}
|
||||
|
||||
async getListens(options: GetListenReportDto): Promise<ListenReportDto> {
|
||||
const { user, timeFrame, timeStart, timeEnd } = options;
|
||||
|
||||
const { items: listens } = await this.listensService.getListens({
|
||||
user,
|
||||
filter: { time: { start: timeStart, end: timeEnd } },
|
||||
page: 1,
|
||||
limit: 10000000,
|
||||
});
|
||||
|
||||
const reportInterval: Interval = {
|
||||
start: parseISO(timeStart),
|
||||
end: parseISO(timeEnd),
|
||||
};
|
||||
|
||||
const { eachOfInterval, isSame } = timeframeToDateFns[timeFrame];
|
||||
|
||||
const reportItems = eachOfInterval(reportInterval).map((date) => {
|
||||
const count = listens.filter((listen) => isSame(date, listen.playedAt))
|
||||
.length;
|
||||
return { date: formatISO(date), count };
|
||||
});
|
||||
|
||||
return { items: reportItems, timeStart, timeEnd, timeFrame };
|
||||
}
|
||||
}
|
||||
6
src/reports/timeframe.enum.ts
Normal file
6
src/reports/timeframe.enum.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export enum Timeframe {
|
||||
Day = "day",
|
||||
Week = "week",
|
||||
Month = "month",
|
||||
Year = "year",
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue