refactor(playlist-backend): refactor playlist backend code
This commit is contained in:
@@ -1,27 +1,34 @@
|
||||
import {Body, Controller, Delete, Get, Param, Post, Put} from '@nestjs/common';
|
||||
import {PlaylistService} from "./playlist.service";
|
||||
import {PlaylistModel} from "./playlist.model";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { PlaylistService } from './playlist.service';
|
||||
import { PlaylistEntity } from './playlist.entity';
|
||||
|
||||
@Controller('playlist')
|
||||
export class PlaylistController {
|
||||
|
||||
constructor(
|
||||
private readonly service: PlaylistService,
|
||||
) {
|
||||
}
|
||||
constructor(private readonly service: PlaylistService) {}
|
||||
|
||||
@Get()
|
||||
getAll(): Promise<PlaylistModel[]> {
|
||||
getAll(): Promise<PlaylistEntity[]> {
|
||||
return this.service.findAll();
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() playlist: PlaylistModel): Promise<void> {
|
||||
async create(@Body() playlist: PlaylistEntity): Promise<void> {
|
||||
await this.service.create(playlist);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
update(@Param('id') id: number, @Body() playlist: Partial<PlaylistModel>): Promise<void> {
|
||||
update(
|
||||
@Param('id') id: number,
|
||||
@Body() playlist: Partial<PlaylistEntity>,
|
||||
): Promise<void> {
|
||||
return this.service.update(id, playlist);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
|
||||
import {TrackEntity} from "../track/track.entity";
|
||||
import { TrackEntity } from '../track/track.entity';
|
||||
|
||||
@Entity()
|
||||
export class PlaylistEntity {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@@ -16,12 +15,12 @@ export class PlaylistEntity {
|
||||
@Column({ nullable: true })
|
||||
error?: string;
|
||||
|
||||
@Column({default: false})
|
||||
@Column({ default: false })
|
||||
active?: boolean;
|
||||
|
||||
@Column({default: () => Date.now()})
|
||||
@Column({ default: () => Date.now() })
|
||||
createdAt?: number;
|
||||
|
||||
@OneToMany(() => TrackEntity, track => track.playlist)
|
||||
@OneToMany(() => TrackEntity, (track) => track.playlist)
|
||||
tracks?: TrackEntity[];
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface PlaylistModel {
|
||||
id: number;
|
||||
name?: string;
|
||||
spotifyUrl: string;
|
||||
tracks?: any[]; //todo fix it
|
||||
error?: string;
|
||||
createdAt?: number;
|
||||
active?: boolean;
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import {PlaylistEntity} from "./playlist.entity";
|
||||
import {PlaylistService} from "./playlist.service";
|
||||
import {PlaylistController} from "./playlist.controller";
|
||||
import {TrackModule} from "../track/track.module";
|
||||
import {ConfigModule} from "@nestjs/config";
|
||||
import { PlaylistEntity } from './playlist.entity';
|
||||
import { PlaylistService } from './playlist.service';
|
||||
import { PlaylistController } from './playlist.controller';
|
||||
import { TrackModule } from '../track/track.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([PlaylistEntity]),
|
||||
ConfigModule,
|
||||
TrackModule,
|
||||
SharedModule,
|
||||
],
|
||||
providers: [PlaylistService],
|
||||
controllers: [PlaylistController],
|
||||
exports: [PlaylistService]
|
||||
exports: [PlaylistService],
|
||||
})
|
||||
export class PlaylistModule {}
|
||||
@@ -1,34 +1,40 @@
|
||||
import {Injectable} from '@nestjs/common';
|
||||
import {InjectRepository} from '@nestjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import {PlaylistEntity} from "./playlist.entity";
|
||||
import {TrackService} from "../track/track.service";
|
||||
import {WebSocketGateway, WebSocketServer} from "@nestjs/websockets";
|
||||
import {Server} from "socket.io";
|
||||
import {resolve} from "path";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {EnviromentEnum} from "../enviroment.enum";
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { PlaylistEntity } from './playlist.entity';
|
||||
import { TrackService } from '../track/track.service';
|
||||
import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
|
||||
import { Server } from 'socket.io';
|
||||
import * as fs from 'fs';
|
||||
import {Interval} from "@nestjs/schedule";
|
||||
import {TrackStatusEnum} from "../track/track.entity";
|
||||
import { Interval } from '@nestjs/schedule';
|
||||
import { TrackStatusEnum } from '../track/track.entity';
|
||||
import { UtilsService } from '../shared/utils.service';
|
||||
const fetch = require('isomorphic-unfetch');
|
||||
const { getData, getPreview, getTracks, getDetails } = require('spotify-url-info')(fetch);
|
||||
const { getDetails } = require('spotify-url-info')(fetch);
|
||||
|
||||
enum WsPlaylistOperation {
|
||||
New = 'playlistNew',
|
||||
Update = 'playlistUpdate',
|
||||
Delete = 'playlistDelete',
|
||||
}
|
||||
|
||||
@WebSocketGateway()
|
||||
@Injectable()
|
||||
export class PlaylistService {
|
||||
|
||||
@WebSocketServer() io: Server;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(PlaylistEntity)
|
||||
private repository: Repository<PlaylistEntity>,
|
||||
private readonly trackService: TrackService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly utilsService: UtilsService,
|
||||
) {}
|
||||
|
||||
findAll(relations: Record<string, boolean> = {tracks: true}, where?: Partial<PlaylistEntity>): Promise<PlaylistEntity[]> {
|
||||
return this.repository.find({where, relations});
|
||||
findAll(
|
||||
relations: Record<string, boolean> = { tracks: true },
|
||||
where?: Partial<PlaylistEntity>,
|
||||
): Promise<PlaylistEntity[]> {
|
||||
return this.repository.find({ where, relations });
|
||||
}
|
||||
|
||||
findOne(id: number): Promise<PlaylistEntity | null> {
|
||||
@@ -37,7 +43,7 @@ export class PlaylistService {
|
||||
|
||||
async remove(id: number): Promise<void> {
|
||||
await this.repository.delete(id);
|
||||
this.io.emit('playlistDelete', {id});
|
||||
this.io.emit(WsPlaylistOperation.Delete, { id });
|
||||
}
|
||||
|
||||
async create(playlist: PlaylistEntity): Promise<void> {
|
||||
@@ -45,66 +51,74 @@ export class PlaylistService {
|
||||
let playlist2Save: PlaylistEntity;
|
||||
try {
|
||||
details = await getDetails(playlist.spotifyUrl);
|
||||
playlist2Save = {...playlist, name: details.preview.title};
|
||||
playlist2Save = { ...playlist, name: details.preview.title };
|
||||
} catch (err) {
|
||||
playlist2Save = {...playlist, error: String(err)};
|
||||
playlist2Save = { ...playlist, error: String(err) };
|
||||
}
|
||||
const savedPlaylist = await this.save(playlist2Save);
|
||||
this.createPlaylistFolderStructure(savedPlaylist.name);
|
||||
for(let track of details?.tracks ?? []) {
|
||||
await this.trackService.create({
|
||||
for (const track of details?.tracks ?? []) {
|
||||
await this.trackService.create(
|
||||
{
|
||||
artist: track.artist,
|
||||
name: track.name,
|
||||
spotifyUrl: track.previewUrl,
|
||||
}, savedPlaylist);
|
||||
},
|
||||
savedPlaylist,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async save(playlist: PlaylistEntity): Promise<PlaylistEntity> {
|
||||
const savedPlaylist = await this.repository.save(playlist);
|
||||
this.io.emit('playlistNew', savedPlaylist);
|
||||
this.io.emit(WsPlaylistOperation.New, savedPlaylist);
|
||||
return savedPlaylist;
|
||||
}
|
||||
|
||||
async update(id: number, playlist: Partial<PlaylistEntity>): Promise<void> {
|
||||
await this.repository.update(id, playlist);
|
||||
const dbPlaylist = await this.findOne(id);
|
||||
this.io.emit('playlistUpdate', dbPlaylist);
|
||||
this.io.emit(WsPlaylistOperation.Update, dbPlaylist);
|
||||
}
|
||||
|
||||
async retryFailedOfPlaylist(id: number): Promise<void> {
|
||||
const tracks = await this.trackService.getAllByPlaylist(id);
|
||||
for(let track of tracks) {
|
||||
for (const track of tracks) {
|
||||
if (track.status === TrackStatusEnum.Error) {
|
||||
await this.trackService.retry(track.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createPlaylistFolderStructure(playlistName): void {
|
||||
const folderName = resolve(__dirname, '..',this.configService.get<string>(EnviromentEnum.DOWNLOADS_PATH), playlistName);
|
||||
!fs.existsSync(folderName) && fs.mkdirSync(folderName);
|
||||
private createPlaylistFolderStructure(playlistName: string): void {
|
||||
const playlistPath = this.utilsService.getPlaylistFolderPath(playlistName);
|
||||
!fs.existsSync(playlistPath) && fs.mkdirSync(playlistPath);
|
||||
}
|
||||
|
||||
@Interval(3600000)
|
||||
@Interval(3_600_000)
|
||||
async checkActivePlaylists(): Promise<void> {
|
||||
const activePlaylists = await this.findAll({}, {active: true});
|
||||
for (let playlist of activePlaylists) {
|
||||
const activePlaylists = await this.findAll({}, { active: true });
|
||||
for (const playlist of activePlaylists) {
|
||||
let details;
|
||||
try {
|
||||
details = await getDetails(playlist.spotifyUrl);
|
||||
} catch (err) {
|
||||
await this.update(playlist.id, {...playlist, error: String(err)});
|
||||
await this.update(playlist.id, { ...playlist, error: String(err) });
|
||||
}
|
||||
this.createPlaylistFolderStructure(playlist.name);
|
||||
for(let track of details?.tracks ?? []) {
|
||||
for (const track of details?.tracks ?? []) {
|
||||
const track2Save = {
|
||||
artist: track.artist,
|
||||
name: track.name,
|
||||
spotifyUrl: track.previewUrl,
|
||||
}
|
||||
const isExist = !!(await this.trackService.findAll({...track2Save, playlist: {id: playlist.id}})).length;
|
||||
if(!isExist) {
|
||||
};
|
||||
const isExist = !!(
|
||||
await this.trackService.findAll({
|
||||
...track2Save,
|
||||
playlist: { id: playlist.id },
|
||||
})
|
||||
).length;
|
||||
if (!isExist) {
|
||||
await this.trackService.create(track2Save, playlist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UtilsService } from './utils.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
providers: [UtilsService],
|
||||
controllers: [],
|
||||
exports: [UtilsService],
|
||||
})
|
||||
export class SharedModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { resolve } from 'path';
|
||||
import { EnviromentEnum } from '../enviroment.enum';
|
||||
|
||||
@Injectable()
|
||||
export class UtilsService {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
getPlaylistFolderPath(name: string): string {
|
||||
return resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
this.configService.get<string>(EnviromentEnum.DOWNLOADS_PATH),
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,14 @@ import { TrackEntity } from './track.entity';
|
||||
import { TrackService } from './track.service';
|
||||
import { TrackController } from './track.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([TrackEntity]), ConfigModule],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([TrackEntity]),
|
||||
ConfigModule,
|
||||
SharedModule,
|
||||
],
|
||||
providers: [TrackService],
|
||||
controllers: [TrackController],
|
||||
exports: [TrackService],
|
||||
|
||||
@@ -13,6 +13,7 @@ import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
|
||||
import { Server } from 'socket.io';
|
||||
import * as ffmpeg from 'fluent-ffmpeg';
|
||||
import { EnviromentEnum } from '../enviroment.enum';
|
||||
import {UtilsService} from "../shared/utils.service";
|
||||
|
||||
enum WsTrackOperation {
|
||||
New = 'trackNew',
|
||||
@@ -30,6 +31,7 @@ export class TrackService {
|
||||
@InjectRepository(TrackEntity)
|
||||
private repository: Repository<TrackEntity>,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly utilsService: UtilsService,
|
||||
) {}
|
||||
|
||||
findAll(
|
||||
@@ -153,10 +155,7 @@ export class TrackService {
|
||||
|
||||
getFolderName(track: TrackEntity, playlist: PlaylistEntity): string {
|
||||
return resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
this.configService.get<string>(EnviromentEnum.DOWNLOADS_PATH),
|
||||
playlist.name,
|
||||
this.utilsService.getPlaylistFolderPath(playlist.name),
|
||||
this.getTrackFileName(track),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user