mirror of
https://github.com/sakaljurgis/kodi-api-server.git
synced 2026-07-08 20:37:41 +00:00
add migrations (package, config, migrations), add exceptions filter for kodi, start VideoFiles module
This commit is contained in:
+8
-4
@@ -1,5 +1,9 @@
|
|||||||
PORT=3000
|
PORT=3000
|
||||||
DB_PATH=./data/db/db.db
|
STATIC_SERVE_FOLDER="data/static"
|
||||||
LOG_FILE_REQUESTS=./data/static/static.log
|
LOG_FILE_REQUESTS="data/static/static_log.txt"
|
||||||
STATIC_SERVE_FOLDER=./data/static
|
DB_PATH="./data/db/db.db"
|
||||||
RECENT_SEARCHES_FOLDER=./data/recent
|
RECENT_SEARCHES_FOLDER="./data/recent_searches"
|
||||||
|
TEST_ARRAY='[
|
||||||
|
"one",
|
||||||
|
"two"
|
||||||
|
]'
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ A rewrite attempt from "vanilla" node.js into framework based (Nest.js).
|
|||||||
|
|
||||||
No licence
|
No licence
|
||||||
|
|
||||||
|
## Migrations
|
||||||
|
```bash
|
||||||
|
# create migration
|
||||||
|
$ npm run typeorm:create-migration -name=CreateFilesTable
|
||||||
|
|
||||||
|
# run migration
|
||||||
|
$ npm run typeorm:run-migrations
|
||||||
|
```
|
||||||
|
|
||||||
## Below are left from nest readme
|
## Below are left from nest readme
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateFilesTable1672729641013 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`create table files
|
||||||
|
(
|
||||||
|
id INTEGER
|
||||||
|
primary key autoincrement,
|
||||||
|
path VARCHAR(512),
|
||||||
|
deleted TINYINT,
|
||||||
|
size INTEGER,
|
||||||
|
file_name VARCHAR(512),
|
||||||
|
infos TEXT,
|
||||||
|
info TEXT,
|
||||||
|
title_id INTEGER,
|
||||||
|
season INTEGER default NULL,
|
||||||
|
duration REAL,
|
||||||
|
transmission INTEGER default NULL,
|
||||||
|
lm INTEGER default NULL
|
||||||
|
);`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`create unique index path
|
||||||
|
on files (path);`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`create index season
|
||||||
|
on files (season);`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`create index title_id
|
||||||
|
on files (title_id);`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE files`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateTitlesTable1672729964237 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`create table titles
|
||||||
|
(
|
||||||
|
id INTEGER
|
||||||
|
primary key,
|
||||||
|
title VARCHAR(512),
|
||||||
|
type VARCHAR(50)
|
||||||
|
);`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`create unique index title_type
|
||||||
|
on titles (title, type);`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE titles`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateLrtCategoriesTable1672730402752
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`create table lrt_categories
|
||||||
|
(
|
||||||
|
category_url TEXT,
|
||||||
|
category_id TEXT
|
||||||
|
);`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.query(
|
||||||
|
`create index lrt_categories_category_url_index
|
||||||
|
on lrt_categories (category_url);`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE lrt_categories`);
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-2
@@ -12,13 +12,18 @@
|
|||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/src/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
|
"typeorm": "ts-node ./node_modules/typeorm/cli",
|
||||||
|
"typeorm:run-migrations": "npm run typeorm migration:run -- -d ./src/config/type-orm-migrations.config.ts",
|
||||||
|
"typeorm:generate-migration": "npm run typeorm -- -d ./src/config/type-orm-migrations.config.ts migration:generate ./migrations/$npm_config_name",
|
||||||
|
"typeorm:create-migration": "npm run typeorm -- migration:create ./migrations/$npm_config_name",
|
||||||
|
"typeorm:revert-migration": "npm run typeorm -- -d ./src/config/type-orm-migrations.config.ts migration:revert"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/axios": "^1.0.0",
|
"@nestjs/axios": "^1.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
ExceptionFilter,
|
||||||
|
Catch,
|
||||||
|
ArgumentsHost,
|
||||||
|
HttpException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
|
@Catch(HttpException)
|
||||||
|
export class HttpExceptionFilter implements ExceptionFilter {
|
||||||
|
catch(exception: HttpException, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<Response>();
|
||||||
|
//const request = ctx.getRequest<Request>();
|
||||||
|
const status = exception.getStatus();
|
||||||
|
|
||||||
|
// 200 response is bad practice,
|
||||||
|
// but currently it is the only way KODi will show what happened
|
||||||
|
response.status(200).json({
|
||||||
|
// original response:
|
||||||
|
response: exception.getResponse(),
|
||||||
|
// for KODI:
|
||||||
|
notification: status + ': ' + exception.message,
|
||||||
|
refreshContainer: false,
|
||||||
|
updateList: false,
|
||||||
|
// msgBoxOK: status + ': ' + exception.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Controller, Get, Head, Param, Req, Res } from '@nestjs/common';
|
import { Controller, Get, Head, Param, Req, Res } from '@nestjs/common';
|
||||||
import ApiResponse from '../Dto/api-response.dto';
|
import ApiResponse from '../Dto/api-response.dto';
|
||||||
import { AllFilesService } from './all-files.service';
|
import { AllFilesService } from './all-files.service';
|
||||||
import { TitleTypeEnum } from './Enum/title-type.enum';
|
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
|
import { TitleTypeEnum } from '../../VideoFiles/Enum/title-type.enum';
|
||||||
|
|
||||||
@Controller('api/all')
|
@Controller('api/all')
|
||||||
export class AllFilesController {
|
export class AllFilesController {
|
||||||
|
|||||||
@@ -2,16 +2,11 @@ import { Module } from '@nestjs/common';
|
|||||||
import { AllFilesController } from './all-files.controller';
|
import { AllFilesController } from './all-files.controller';
|
||||||
import { AllFilesService } from './all-files.service';
|
import { AllFilesService } from './all-files.service';
|
||||||
import { KodiApiResponseFactory } from '../kodi-api-response.factory';
|
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 { StreamerModule } from '../../Streamer/streamer.module';
|
||||||
|
import { VideoFilesModule } from '../../VideoFiles/video-files.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [StreamerModule, VideoFilesModule],
|
||||||
TypeOrmModule.forFeature([TitleEntity, FileEntity]),
|
|
||||||
StreamerModule,
|
|
||||||
],
|
|
||||||
controllers: [AllFilesController],
|
controllers: [AllFilesController],
|
||||||
providers: [AllFilesService, KodiApiResponseFactory],
|
providers: [AllFilesService, KodiApiResponseFactory],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import ApiResponse from '../Dto/api-response.dto';
|
import ApiResponse from '../Dto/api-response.dto';
|
||||||
import { KodiApiResponseFactory } from '../kodi-api-response.factory';
|
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 { StreamerFacade } from '../../Streamer/streamer.facade';
|
||||||
import { Request, Response } from 'express';
|
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()
|
@Injectable()
|
||||||
export class AllFilesService {
|
export class AllFilesService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly koiApiResponseFactory: KodiApiResponseFactory,
|
private readonly koiApiResponseFactory: KodiApiResponseFactory,
|
||||||
@InjectRepository(TitleEntity)
|
|
||||||
private titleRepository: Repository<TitleEntity>,
|
|
||||||
@InjectRepository(FileEntity)
|
|
||||||
private fileRepository: Repository<FileEntity>,
|
|
||||||
private readonly streamerFacade: StreamerFacade,
|
private readonly streamerFacade: StreamerFacade,
|
||||||
|
private readonly videoTitlesProvider: VideoFilesProvider,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
getMenu(): ApiResponse {
|
getMenu(): ApiResponse {
|
||||||
@@ -37,13 +33,7 @@ export class AllFilesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getListOfTitles(type: TitleTypeEnum): Promise<ApiResponse> {
|
async getListOfTitles(type: TitleTypeEnum): Promise<ApiResponse> {
|
||||||
const entities = await this.titleRepository
|
const entities = await this.videoTitlesProvider.getListOfTitles(type);
|
||||||
.createQueryBuilder()
|
|
||||||
.setFindOptions({ relations: { files: true } })
|
|
||||||
.where('type = :type', { type: type })
|
|
||||||
.andWhere('deleted = :deleted', { deleted: false })
|
|
||||||
.getMany();
|
|
||||||
|
|
||||||
const response = this.koiApiResponseFactory.createApiResponse();
|
const response = this.koiApiResponseFactory.createApiResponse();
|
||||||
response.setTitle('All ' + type + 's');
|
response.setTitle('All ' + type + 's');
|
||||||
entities.forEach((entity) => {
|
entities.forEach((entity) => {
|
||||||
@@ -51,7 +41,9 @@ export class AllFilesService {
|
|||||||
.createItem()
|
.createItem()
|
||||||
.setLabel(entity.title)
|
.setLabel(entity.title)
|
||||||
.setToFolder()
|
.setToFolder()
|
||||||
.setPlot(entity.files.length + ' files')
|
.setPlot(
|
||||||
|
entity.files.length + (entity.files.length > 1 ? ' files' : ' file'),
|
||||||
|
)
|
||||||
.setPath('all/' + entity.id);
|
.setPath('all/' + entity.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,15 +51,15 @@ export class AllFilesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadTitle(titleId: number, seasonId: number): Promise<ApiResponse> {
|
async loadTitle(titleId: number, seasonId: number): Promise<ApiResponse> {
|
||||||
const titleEntity = await this.titleRepository.findOne({
|
const titleEntity = titleId
|
||||||
relations: { files: true },
|
? await this.videoTitlesProvider.getTitleWithFiles(titleId)
|
||||||
where: { id: titleId },
|
: null;
|
||||||
});
|
|
||||||
|
|
||||||
if (titleEntity === null) {
|
if (titleEntity === null) {
|
||||||
|
//todo - alert instead of full api response
|
||||||
return this.koiApiResponseFactory
|
return this.koiApiResponseFactory
|
||||||
.createApiResponse()
|
.createApiResponse()
|
||||||
.setTitle('Not found ' + titleId);
|
.setTitle('Not found' + (titleId ? ' ' + titleId : ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (titleEntity.type === TitleTypeEnum.movie) {
|
if (titleEntity.type === TitleTypeEnum.movie) {
|
||||||
@@ -112,10 +104,13 @@ export class AllFilesService {
|
|||||||
|
|
||||||
//todo - add num files count
|
//todo - add num files count
|
||||||
const seasons = [];
|
const seasons = [];
|
||||||
|
const filesCount = {};
|
||||||
for (const fileEntity of titleEntity.files) {
|
for (const fileEntity of titleEntity.files) {
|
||||||
if (seasons.indexOf(fileEntity.season) === -1) {
|
if (seasons.indexOf(fileEntity.season) === -1) {
|
||||||
seasons.push(fileEntity.season);
|
seasons.push(fileEntity.season);
|
||||||
|
filesCount[fileEntity.season] = 0;
|
||||||
}
|
}
|
||||||
|
filesCount[fileEntity.season]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const season of seasons) {
|
for (const season of seasons) {
|
||||||
@@ -123,6 +118,9 @@ export class AllFilesService {
|
|||||||
.createItem()
|
.createItem()
|
||||||
.setLabel(season + ' Sezonas')
|
.setLabel(season + ' Sezonas')
|
||||||
.setToFolder()
|
.setToFolder()
|
||||||
|
.setPlot(
|
||||||
|
filesCount[season] + (filesCount[season] > 1 ? ' files' : ' file'),
|
||||||
|
)
|
||||||
.setPath(`all/${titleEntity.id}/${season}`);
|
.setPath(`all/${titleEntity.id}/${season}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,13 +136,12 @@ export class AllFilesService {
|
|||||||
if (isNaN(id)) {
|
if (isNaN(id)) {
|
||||||
throw new NotFoundException(`id #${fileId} not correct`);
|
throw new NotFoundException(`id #${fileId} not correct`);
|
||||||
}
|
}
|
||||||
|
const filePath = await this.videoTitlesProvider.getFilePath(id);
|
||||||
|
|
||||||
const entity = await this.fileRepository.findOne({ where: { id: id } });
|
if (filePath === null) {
|
||||||
|
|
||||||
if (entity === null) {
|
|
||||||
throw new NotFoundException(`id #${fileId} not found`);
|
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> = [];
|
items: Array<ResponseItem> = [];
|
||||||
content = 'videos';
|
content = 'videos';
|
||||||
updateList = true;
|
updateList = true;
|
||||||
category: string;
|
category: string | undefined;
|
||||||
play: string;
|
play: string | undefined;
|
||||||
msgBoxOK: string;
|
msgBoxOK: string | undefined;
|
||||||
nosort: boolean;
|
nosort: boolean | undefined;
|
||||||
|
|
||||||
setTitle(title) {
|
setTitle(title) {
|
||||||
this.category = title;
|
this.category = title;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export class LrtApiSearchClient {
|
|||||||
const searchResponse: SearchResponseInterface = resp.data;
|
const searchResponse: SearchResponseInterface = resp.data;
|
||||||
|
|
||||||
const cats = [];
|
const cats = [];
|
||||||
|
const loadedCategories = new Set();
|
||||||
|
|
||||||
//todo - move to mapper
|
//todo - move to mapper
|
||||||
const responseDto = new SearchResponseDto(
|
const responseDto = new SearchResponseDto(
|
||||||
@@ -36,6 +37,9 @@ export class LrtApiSearchClient {
|
|||||||
if (cats.indexOf(searchItem.category_title) > -1) {
|
if (cats.indexOf(searchItem.category_title) > -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (loadedCategories.has(searchItem.category_title)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const itemDto = new SearchResponseItemDto();
|
const itemDto = new SearchResponseItemDto();
|
||||||
itemDto.label = searchItem.category_title;
|
itemDto.label = searchItem.category_title;
|
||||||
itemDto.thumb =
|
itemDto.thumb =
|
||||||
@@ -46,6 +50,7 @@ export class LrtApiSearchClient {
|
|||||||
itemDto.categoryId = await this.findCategoryId(searchItem.category_url);
|
itemDto.categoryId = await this.findCategoryId(searchItem.category_url);
|
||||||
|
|
||||||
cats.push(searchItem.category_title);
|
cats.push(searchItem.category_title);
|
||||||
|
loadedCategories.add(searchItem.category_title);
|
||||||
responseDto.addItem(itemDto);
|
responseDto.addItem(itemDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +88,6 @@ export class LrtApiSearchClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async findCategoryIdInLrt(categoryUrl: string): Promise<string> {
|
private async findCategoryIdInLrt(categoryUrl: string): Promise<string> {
|
||||||
//todo - cache to database
|
|
||||||
const url = 'https://www.lrt.lt' + categoryUrl;
|
const url = 'https://www.lrt.lt' + categoryUrl;
|
||||||
const resp = await firstValueFrom(this.httpService.get(url));
|
const resp = await firstValueFrom(this.httpService.get(url));
|
||||||
const body: string = resp.data;
|
const body: string = resp.data;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity('lrt_categories')
|
||||||
export class LrtCategory {
|
export class LrtCategory {
|
||||||
@PrimaryColumn({ name: 'category_url' })
|
@PrimaryColumn({ name: 'category_url' })
|
||||||
categoryUrl: string;
|
categoryUrl: string;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { StaticService } from './static.service';
|
||||||
|
|
||||||
|
describe('StaticService', () => {
|
||||||
|
let staticService: StaticService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
staticService = new StaticService();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('provideDirIndex', () => {
|
||||||
|
it('should return file/dir list of dir', async () => {
|
||||||
|
jest.spyOn(staticService, 'provideDirIndex');
|
||||||
|
process.env.STATIC_SERVE_FOLDER = 'test/Static/_test-data/StaticServe';
|
||||||
|
|
||||||
|
const result = await staticService.provideDirIndex('');
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get } from '@nestjs/common';
|
||||||
import { EntityManager } from 'typeorm';
|
|
||||||
|
|
||||||
@Controller('try')
|
@Controller('try')
|
||||||
export class TryController {
|
export class TryController {
|
||||||
constructor(private orm: EntityManager) {}
|
|
||||||
@Get('')
|
@Get('')
|
||||||
async main() {
|
main() {
|
||||||
return await this.orm.query('select * from aliases');
|
return process.env;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { TryController } from './try.cotroller';
|
import { TryController } from './try.cotroller';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ export class FileEntity {
|
|||||||
@Column()
|
@Column()
|
||||||
size: number;
|
size: number;
|
||||||
|
|
||||||
@Column({ name: 'relative_path' })
|
|
||||||
relativePath: string;
|
|
||||||
|
|
||||||
@Column({ name: 'file_name' })
|
@Column({ name: 'file_name' })
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TitleEntity } from './Entity/title.entity';
|
||||||
|
import { FileEntity } from './Entity/file.entity';
|
||||||
|
import { VideoFilesProvider } from './video-files.provider';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TitleEntity, FileEntity])],
|
||||||
|
controllers: [],
|
||||||
|
providers: [VideoFilesProvider],
|
||||||
|
exports: [VideoFilesProvider],
|
||||||
|
})
|
||||||
|
export class VideoFilesModule {}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { TitleEntity } from './Entity/title.entity';
|
||||||
|
import { TitleTypeEnum } from './Enum/title-type.enum';
|
||||||
|
import { FileEntity } from './Entity/file.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class VideoFilesProvider {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(TitleEntity)
|
||||||
|
private readonly titleRepository: Repository<TitleEntity>,
|
||||||
|
@InjectRepository(FileEntity)
|
||||||
|
private readonly fileRepository: Repository<FileEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async getListOfTitles(type: TitleTypeEnum): Promise<TitleEntity[]> {
|
||||||
|
return this.titleRepository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.setFindOptions({ relations: { files: true } })
|
||||||
|
.where('type = :type', { type: type })
|
||||||
|
.andWhere('deleted = :deleted', { deleted: false })
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTitleWithFiles(titleId: number): Promise<TitleEntity | null> {
|
||||||
|
return this.titleRepository
|
||||||
|
.findOne({
|
||||||
|
relations: { files: true },
|
||||||
|
where: { id: titleId },
|
||||||
|
})
|
||||||
|
.catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFilePath(fileId: number): Promise<string | null> {
|
||||||
|
const entity = await this.fileRepository
|
||||||
|
.findOne({ where: { id: fileId } })
|
||||||
|
.catch(() => null);
|
||||||
|
|
||||||
|
if (entity === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-13
@@ -7,27 +7,33 @@ export class PathsConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getStaticRequestsLogPath(): string {
|
public getStaticRequestsLogPath(): string {
|
||||||
return join(
|
return this.getPathByEnvKey('LOG_FILE_REQUESTS');
|
||||||
this.getRootPath(),
|
|
||||||
this.configService.getEnv('LOG_FILE_REQUESTS'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStaticFolder(): string {
|
public getStaticFolder(): string {
|
||||||
return join(
|
return this.getPathByEnvKey('STATIC_SERVE_FOLDER');
|
||||||
this.getRootPath(),
|
|
||||||
this.configService.getEnv('STATIC_SERVE_FOLDER'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRecentSearchesFolder(): string {
|
public getRecentSearchesFolder(): string {
|
||||||
return join(
|
return this.getPathByEnvKey('RECENT_SEARCHES_FOLDER');
|
||||||
this.getRootPath(),
|
}
|
||||||
this.configService.getEnv('RECENT_SEARCHES_FOLDER'),
|
|
||||||
);
|
private getPathByEnvKey(key: string) {
|
||||||
|
const relPath = this.configService.getEnv(key);
|
||||||
|
|
||||||
|
return this.getPath(relPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPath(relPath: string) {
|
||||||
|
if (relPath[0] === '/') {
|
||||||
|
//this is an absolute path
|
||||||
|
return relPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return join(this.getRootPath(), relPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRootPath(): string {
|
private getRootPath(): string {
|
||||||
return join(__dirname, '../..');
|
return this.configService.getEnv('PWD');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||||
|
import { configService } from './config.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* config for typeorm cli
|
||||||
|
*/
|
||||||
|
export default new DataSource(<DataSourceOptions>{
|
||||||
|
type: configService.getTypeOrmConfig().type,
|
||||||
|
database: configService.getTypeOrmConfig().database,
|
||||||
|
migrations: ['migrations/*{.ts,.js}'],
|
||||||
|
});
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { LrtCategory } from '../KodiApi/LRT/LrtApiClient/Entity/lrt-category.entity';
|
import { LrtCategory } from '../KodiApi/LRT/LrtApiClient/Entity/lrt-category.entity';
|
||||||
import { TitleEntity } from '../KodiApi/AllFiles/Entity/title.entity';
|
|
||||||
import { FileEntity } from '../KodiApi/AllFiles/Entity/file.entity';
|
|
||||||
import { ConfigService } from './config.service';
|
import { ConfigService } from './config.service';
|
||||||
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';
|
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';
|
||||||
|
import { TitleEntity } from '../VideoFiles/Entity/title.entity';
|
||||||
|
import { FileEntity } from '../VideoFiles/Entity/file.entity';
|
||||||
|
|
||||||
export class TypeOrmConfig {
|
export class TypeOrmConfig {
|
||||||
constructor(configService: ConfigService) {
|
constructor(configService: ConfigService) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { configService } from './config/config.service';
|
import { configService } from './config/config.service';
|
||||||
|
import { HttpExceptionFilter } from './Exception/http-exception.filter';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
app.useGlobalFilters(new HttpExceptionFilter());
|
||||||
await app.listen(configService.getPort());
|
await app.listen(configService.getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user