mirror of
https://github.com/sakaljurgis/kodi-api-server.git
synced 2026-07-09 04:47:41 +00:00
add migrations (package, config, migrations), add exceptions filter for kodi, start VideoFiles module
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { TitleEntity } from './title.entity';
|
||||
|
||||
@Entity('files')
|
||||
export class FileEntity {
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
path: string;
|
||||
|
||||
@Column()
|
||||
deleted: boolean;
|
||||
|
||||
@Column()
|
||||
size: number;
|
||||
|
||||
@Column({ name: 'relative_path' })
|
||||
relativePath: string;
|
||||
|
||||
@Column({ name: 'file_name' })
|
||||
fileName: string;
|
||||
|
||||
@Column()
|
||||
infos: string;
|
||||
|
||||
@Column()
|
||||
info: string;
|
||||
|
||||
@Column({ name: 'title_id' })
|
||||
titleId: number;
|
||||
|
||||
@ManyToOne(() => TitleEntity, (title) => title.files)
|
||||
@JoinColumn({ name: 'title_id' })
|
||||
title: TitleEntity;
|
||||
|
||||
@Column()
|
||||
season: number | null;
|
||||
|
||||
@Column()
|
||||
duration: number;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Column, Entity, OneToMany, PrimaryColumn } from 'typeorm';
|
||||
import { TitleTypeEnum } from '../Enum/title-type.enum';
|
||||
import { FileEntity } from './file.entity';
|
||||
|
||||
@Entity('titles')
|
||||
export class TitleEntity {
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column()
|
||||
type: TitleTypeEnum;
|
||||
|
||||
@OneToMany(() => FileEntity, (file) => file.title)
|
||||
files: FileEntity[];
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export enum TitleTypeEnum {
|
||||
movie = 'movie',
|
||||
show = 'show',
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Controller, Get, Head, Param, Req, Res } from '@nestjs/common';
|
||||
import ApiResponse from '../Dto/api-response.dto';
|
||||
import { AllFilesService } from './all-files.service';
|
||||
import { TitleTypeEnum } from './Enum/title-type.enum';
|
||||
import { Request, Response } from 'express';
|
||||
import { TitleTypeEnum } from '../../VideoFiles/Enum/title-type.enum';
|
||||
|
||||
@Controller('api/all')
|
||||
export class AllFilesController {
|
||||
|
||||
@@ -2,16 +2,11 @@ import { Module } from '@nestjs/common';
|
||||
import { AllFilesController } from './all-files.controller';
|
||||
import { AllFilesService } from './all-files.service';
|
||||
import { KodiApiResponseFactory } from '../kodi-api-response.factory';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { TitleEntity } from './Entity/title.entity';
|
||||
import { FileEntity } from './Entity/file.entity';
|
||||
import { StreamerModule } from '../../Streamer/streamer.module';
|
||||
import { VideoFilesModule } from '../../VideoFiles/video-files.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([TitleEntity, FileEntity]),
|
||||
StreamerModule,
|
||||
],
|
||||
imports: [StreamerModule, VideoFilesModule],
|
||||
controllers: [AllFilesController],
|
||||
providers: [AllFilesService, KodiApiResponseFactory],
|
||||
})
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import ApiResponse from '../Dto/api-response.dto';
|
||||
import { KodiApiResponseFactory } from '../kodi-api-response.factory';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { TitleEntity } from './Entity/title.entity';
|
||||
import { FileEntity } from './Entity/file.entity';
|
||||
import { TitleTypeEnum } from './Enum/title-type.enum';
|
||||
import { StreamerFacade } from '../../Streamer/streamer.facade';
|
||||
import { Request, Response } from 'express';
|
||||
import { VideoFilesProvider } from '../../VideoFiles/video-files.provider';
|
||||
import { TitleTypeEnum } from '../../VideoFiles/Enum/title-type.enum';
|
||||
import { FileEntity } from '../../VideoFiles/Entity/file.entity';
|
||||
import { TitleEntity } from '../../VideoFiles/Entity/title.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AllFilesService {
|
||||
constructor(
|
||||
private readonly koiApiResponseFactory: KodiApiResponseFactory,
|
||||
@InjectRepository(TitleEntity)
|
||||
private titleRepository: Repository<TitleEntity>,
|
||||
@InjectRepository(FileEntity)
|
||||
private fileRepository: Repository<FileEntity>,
|
||||
private readonly streamerFacade: StreamerFacade,
|
||||
private readonly videoTitlesProvider: VideoFilesProvider,
|
||||
) {}
|
||||
|
||||
getMenu(): ApiResponse {
|
||||
@@ -37,13 +33,7 @@ export class AllFilesService {
|
||||
}
|
||||
|
||||
async getListOfTitles(type: TitleTypeEnum): Promise<ApiResponse> {
|
||||
const entities = await this.titleRepository
|
||||
.createQueryBuilder()
|
||||
.setFindOptions({ relations: { files: true } })
|
||||
.where('type = :type', { type: type })
|
||||
.andWhere('deleted = :deleted', { deleted: false })
|
||||
.getMany();
|
||||
|
||||
const entities = await this.videoTitlesProvider.getListOfTitles(type);
|
||||
const response = this.koiApiResponseFactory.createApiResponse();
|
||||
response.setTitle('All ' + type + 's');
|
||||
entities.forEach((entity) => {
|
||||
@@ -51,7 +41,9 @@ export class AllFilesService {
|
||||
.createItem()
|
||||
.setLabel(entity.title)
|
||||
.setToFolder()
|
||||
.setPlot(entity.files.length + ' files')
|
||||
.setPlot(
|
||||
entity.files.length + (entity.files.length > 1 ? ' files' : ' file'),
|
||||
)
|
||||
.setPath('all/' + entity.id);
|
||||
});
|
||||
|
||||
@@ -59,15 +51,15 @@ export class AllFilesService {
|
||||
}
|
||||
|
||||
async loadTitle(titleId: number, seasonId: number): Promise<ApiResponse> {
|
||||
const titleEntity = await this.titleRepository.findOne({
|
||||
relations: { files: true },
|
||||
where: { id: titleId },
|
||||
});
|
||||
const titleEntity = titleId
|
||||
? await this.videoTitlesProvider.getTitleWithFiles(titleId)
|
||||
: null;
|
||||
|
||||
if (titleEntity === null) {
|
||||
//todo - alert instead of full api response
|
||||
return this.koiApiResponseFactory
|
||||
.createApiResponse()
|
||||
.setTitle('Not found ' + titleId);
|
||||
.setTitle('Not found' + (titleId ? ' ' + titleId : ''));
|
||||
}
|
||||
|
||||
if (titleEntity.type === TitleTypeEnum.movie) {
|
||||
@@ -112,10 +104,13 @@ export class AllFilesService {
|
||||
|
||||
//todo - add num files count
|
||||
const seasons = [];
|
||||
const filesCount = {};
|
||||
for (const fileEntity of titleEntity.files) {
|
||||
if (seasons.indexOf(fileEntity.season) === -1) {
|
||||
seasons.push(fileEntity.season);
|
||||
filesCount[fileEntity.season] = 0;
|
||||
}
|
||||
filesCount[fileEntity.season]++;
|
||||
}
|
||||
|
||||
for (const season of seasons) {
|
||||
@@ -123,6 +118,9 @@ export class AllFilesService {
|
||||
.createItem()
|
||||
.setLabel(season + ' Sezonas')
|
||||
.setToFolder()
|
||||
.setPlot(
|
||||
filesCount[season] + (filesCount[season] > 1 ? ' files' : ' file'),
|
||||
)
|
||||
.setPath(`all/${titleEntity.id}/${season}`);
|
||||
}
|
||||
|
||||
@@ -138,13 +136,12 @@ export class AllFilesService {
|
||||
if (isNaN(id)) {
|
||||
throw new NotFoundException(`id #${fileId} not correct`);
|
||||
}
|
||||
const filePath = await this.videoTitlesProvider.getFilePath(id);
|
||||
|
||||
const entity = await this.fileRepository.findOne({ where: { id: id } });
|
||||
|
||||
if (entity === null) {
|
||||
if (filePath === null) {
|
||||
throw new NotFoundException(`id #${fileId} not found`);
|
||||
}
|
||||
|
||||
return this.streamerFacade.streamFile(request, response, entity.path);
|
||||
return this.streamerFacade.streamFile(request, response, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ export default class ApiResponse {
|
||||
items: Array<ResponseItem> = [];
|
||||
content = 'videos';
|
||||
updateList = true;
|
||||
category: string;
|
||||
play: string;
|
||||
msgBoxOK: string;
|
||||
nosort: boolean;
|
||||
category: string | undefined;
|
||||
play: string | undefined;
|
||||
msgBoxOK: string | undefined;
|
||||
nosort: boolean | undefined;
|
||||
|
||||
setTitle(title) {
|
||||
this.category = title;
|
||||
|
||||
@@ -25,6 +25,7 @@ export class LrtApiSearchClient {
|
||||
const searchResponse: SearchResponseInterface = resp.data;
|
||||
|
||||
const cats = [];
|
||||
const loadedCategories = new Set();
|
||||
|
||||
//todo - move to mapper
|
||||
const responseDto = new SearchResponseDto(
|
||||
@@ -36,6 +37,9 @@ export class LrtApiSearchClient {
|
||||
if (cats.indexOf(searchItem.category_title) > -1) {
|
||||
continue;
|
||||
}
|
||||
if (loadedCategories.has(searchItem.category_title)) {
|
||||
continue;
|
||||
}
|
||||
const itemDto = new SearchResponseItemDto();
|
||||
itemDto.label = searchItem.category_title;
|
||||
itemDto.thumb =
|
||||
@@ -46,6 +50,7 @@ export class LrtApiSearchClient {
|
||||
itemDto.categoryId = await this.findCategoryId(searchItem.category_url);
|
||||
|
||||
cats.push(searchItem.category_title);
|
||||
loadedCategories.add(searchItem.category_title);
|
||||
responseDto.addItem(itemDto);
|
||||
}
|
||||
|
||||
@@ -83,7 +88,6 @@ export class LrtApiSearchClient {
|
||||
}
|
||||
|
||||
private async findCategoryIdInLrt(categoryUrl: string): Promise<string> {
|
||||
//todo - cache to database
|
||||
const url = 'https://www.lrt.lt' + categoryUrl;
|
||||
const resp = await firstValueFrom(this.httpService.get(url));
|
||||
const body: string = resp.data;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity('lrt_categories')
|
||||
export class LrtCategory {
|
||||
@PrimaryColumn({ name: 'category_url' })
|
||||
categoryUrl: string;
|
||||
|
||||
Reference in New Issue
Block a user