refactor(playlist-backend): refactor playlist backend code

This commit is contained in:
raiper34
2024-09-30 13:55:08 +02:00
parent 0e88627ec7
commit 117110fae9
10 changed files with 222 additions and 176 deletions
+18 -11
View File
@@ -1,27 +1,34 @@
import {Body, Controller, Delete, Get, Param, Post, Put} from '@nestjs/common'; import {
import {PlaylistService} from "./playlist.service"; Body,
import {PlaylistModel} from "./playlist.model"; Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { PlaylistService } from './playlist.service';
import { PlaylistEntity } from './playlist.entity';
@Controller('playlist') @Controller('playlist')
export class PlaylistController { export class PlaylistController {
constructor(private readonly service: PlaylistService) {}
constructor(
private readonly service: PlaylistService,
) {
}
@Get() @Get()
getAll(): Promise<PlaylistModel[]> { getAll(): Promise<PlaylistEntity[]> {
return this.service.findAll(); return this.service.findAll();
} }
@Post() @Post()
async create(@Body() playlist: PlaylistModel): Promise<void> { async create(@Body() playlist: PlaylistEntity): Promise<void> {
await this.service.create(playlist); await this.service.create(playlist);
} }
@Put(':id') @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); return this.service.update(id, playlist);
} }
+2 -3
View File
@@ -1,9 +1,8 @@
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import {TrackEntity} from "../track/track.entity"; import { TrackEntity } from '../track/track.entity';
@Entity() @Entity()
export class PlaylistEntity { export class PlaylistEntity {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@@ -22,6 +21,6 @@ export class PlaylistEntity {
@Column({ default: () => Date.now() }) @Column({ default: () => Date.now() })
createdAt?: number; createdAt?: number;
@OneToMany(() => TrackEntity, track => track.playlist) @OneToMany(() => TrackEntity, (track) => track.playlist)
tracks?: TrackEntity[]; 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;
}
+8 -6
View File
@@ -1,19 +1,21 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import {PlaylistEntity} from "./playlist.entity"; import { PlaylistEntity } from './playlist.entity';
import {PlaylistService} from "./playlist.service"; import { PlaylistService } from './playlist.service';
import {PlaylistController} from "./playlist.controller"; import { PlaylistController } from './playlist.controller';
import {TrackModule} from "../track/track.module"; import { TrackModule } from '../track/track.module';
import {ConfigModule} from "@nestjs/config"; import { ConfigModule } from '@nestjs/config';
import { SharedModule } from '../shared/shared.module';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([PlaylistEntity]), TypeOrmModule.forFeature([PlaylistEntity]),
ConfigModule, ConfigModule,
TrackModule, TrackModule,
SharedModule,
], ],
providers: [PlaylistService], providers: [PlaylistService],
controllers: [PlaylistController], controllers: [PlaylistController],
exports: [PlaylistService] exports: [PlaylistService],
}) })
export class PlaylistModule {} export class PlaylistModule {}
+42 -28
View File
@@ -1,33 +1,39 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import {PlaylistEntity} from "./playlist.entity"; import { PlaylistEntity } from './playlist.entity';
import {TrackService} from "../track/track.service"; import { TrackService } from '../track/track.service';
import {WebSocketGateway, WebSocketServer} from "@nestjs/websockets"; import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import {Server} from "socket.io"; import { Server } from 'socket.io';
import {resolve} from "path";
import {ConfigService} from "@nestjs/config";
import {EnviromentEnum} from "../enviroment.enum";
import * as fs from 'fs'; import * as fs from 'fs';
import {Interval} from "@nestjs/schedule"; import { Interval } from '@nestjs/schedule';
import {TrackStatusEnum} from "../track/track.entity"; import { TrackStatusEnum } from '../track/track.entity';
import { UtilsService } from '../shared/utils.service';
const fetch = require('isomorphic-unfetch'); 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() @WebSocketGateway()
@Injectable() @Injectable()
export class PlaylistService { export class PlaylistService {
@WebSocketServer() io: Server; @WebSocketServer() io: Server;
constructor( constructor(
@InjectRepository(PlaylistEntity) @InjectRepository(PlaylistEntity)
private repository: Repository<PlaylistEntity>, private repository: Repository<PlaylistEntity>,
private readonly trackService: TrackService, private readonly trackService: TrackService,
private readonly configService: ConfigService, private readonly utilsService: UtilsService,
) {} ) {}
findAll(relations: Record<string, boolean> = {tracks: true}, where?: Partial<PlaylistEntity>): Promise<PlaylistEntity[]> { findAll(
relations: Record<string, boolean> = { tracks: true },
where?: Partial<PlaylistEntity>,
): Promise<PlaylistEntity[]> {
return this.repository.find({ where, relations }); return this.repository.find({ where, relations });
} }
@@ -37,7 +43,7 @@ export class PlaylistService {
async remove(id: number): Promise<void> { async remove(id: number): Promise<void> {
await this.repository.delete(id); await this.repository.delete(id);
this.io.emit('playlistDelete', {id}); this.io.emit(WsPlaylistOperation.Delete, { id });
} }
async create(playlist: PlaylistEntity): Promise<void> { async create(playlist: PlaylistEntity): Promise<void> {
@@ -51,45 +57,48 @@ export class PlaylistService {
} }
const savedPlaylist = await this.save(playlist2Save); const savedPlaylist = await this.save(playlist2Save);
this.createPlaylistFolderStructure(savedPlaylist.name); this.createPlaylistFolderStructure(savedPlaylist.name);
for(let track of details?.tracks ?? []) { for (const track of details?.tracks ?? []) {
await this.trackService.create({ await this.trackService.create(
{
artist: track.artist, artist: track.artist,
name: track.name, name: track.name,
spotifyUrl: track.previewUrl, spotifyUrl: track.previewUrl,
}, savedPlaylist); },
savedPlaylist,
);
} }
} }
async save(playlist: PlaylistEntity): Promise<PlaylistEntity> { async save(playlist: PlaylistEntity): Promise<PlaylistEntity> {
const savedPlaylist = await this.repository.save(playlist); const savedPlaylist = await this.repository.save(playlist);
this.io.emit('playlistNew', savedPlaylist); this.io.emit(WsPlaylistOperation.New, savedPlaylist);
return savedPlaylist; return savedPlaylist;
} }
async update(id: number, playlist: Partial<PlaylistEntity>): Promise<void> { async update(id: number, playlist: Partial<PlaylistEntity>): Promise<void> {
await this.repository.update(id, playlist); await this.repository.update(id, playlist);
const dbPlaylist = await this.findOne(id); const dbPlaylist = await this.findOne(id);
this.io.emit('playlistUpdate', dbPlaylist); this.io.emit(WsPlaylistOperation.Update, dbPlaylist);
} }
async retryFailedOfPlaylist(id: number): Promise<void> { async retryFailedOfPlaylist(id: number): Promise<void> {
const tracks = await this.trackService.getAllByPlaylist(id); const tracks = await this.trackService.getAllByPlaylist(id);
for(let track of tracks) { for (const track of tracks) {
if (track.status === TrackStatusEnum.Error) { if (track.status === TrackStatusEnum.Error) {
await this.trackService.retry(track.id); await this.trackService.retry(track.id);
} }
} }
} }
private createPlaylistFolderStructure(playlistName): void { private createPlaylistFolderStructure(playlistName: string): void {
const folderName = resolve(__dirname, '..',this.configService.get<string>(EnviromentEnum.DOWNLOADS_PATH), playlistName); const playlistPath = this.utilsService.getPlaylistFolderPath(playlistName);
!fs.existsSync(folderName) && fs.mkdirSync(folderName); !fs.existsSync(playlistPath) && fs.mkdirSync(playlistPath);
} }
@Interval(3600000) @Interval(3_600_000)
async checkActivePlaylists(): Promise<void> { async checkActivePlaylists(): Promise<void> {
const activePlaylists = await this.findAll({}, { active: true }); const activePlaylists = await this.findAll({}, { active: true });
for (let playlist of activePlaylists) { for (const playlist of activePlaylists) {
let details; let details;
try { try {
details = await getDetails(playlist.spotifyUrl); details = await getDetails(playlist.spotifyUrl);
@@ -97,13 +106,18 @@ export class PlaylistService {
await this.update(playlist.id, { ...playlist, error: String(err) }); await this.update(playlist.id, { ...playlist, error: String(err) });
} }
this.createPlaylistFolderStructure(playlist.name); this.createPlaylistFolderStructure(playlist.name);
for(let track of details?.tracks ?? []) { for (const track of details?.tracks ?? []) {
const track2Save = { const track2Save = {
artist: track.artist, artist: track.artist,
name: track.name, name: track.name,
spotifyUrl: track.previewUrl, spotifyUrl: track.previewUrl,
} };
const isExist = !!(await this.trackService.findAll({...track2Save, playlist: {id: playlist.id}})).length; const isExist = !!(
await this.trackService.findAll({
...track2Save,
playlist: { id: playlist.id },
})
).length;
if (!isExist) { if (!isExist) {
await this.trackService.create(track2Save, playlist); await this.trackService.create(track2Save, playlist);
} }
+11
View File
@@ -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 {}
+18
View File
@@ -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,
);
}
}
+6 -1
View File
@@ -4,9 +4,14 @@ import { TrackEntity } from './track.entity';
import { TrackService } from './track.service'; import { TrackService } from './track.service';
import { TrackController } from './track.controller'; import { TrackController } from './track.controller';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { SharedModule } from '../shared/shared.module';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([TrackEntity]), ConfigModule], imports: [
TypeOrmModule.forFeature([TrackEntity]),
ConfigModule,
SharedModule,
],
providers: [TrackService], providers: [TrackService],
controllers: [TrackController], controllers: [TrackController],
exports: [TrackService], exports: [TrackService],
+3 -4
View File
@@ -13,6 +13,7 @@ import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server } from 'socket.io'; import { Server } from 'socket.io';
import * as ffmpeg from 'fluent-ffmpeg'; import * as ffmpeg from 'fluent-ffmpeg';
import { EnviromentEnum } from '../enviroment.enum'; import { EnviromentEnum } from '../enviroment.enum';
import {UtilsService} from "../shared/utils.service";
enum WsTrackOperation { enum WsTrackOperation {
New = 'trackNew', New = 'trackNew',
@@ -30,6 +31,7 @@ export class TrackService {
@InjectRepository(TrackEntity) @InjectRepository(TrackEntity)
private repository: Repository<TrackEntity>, private repository: Repository<TrackEntity>,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly utilsService: UtilsService,
) {} ) {}
findAll( findAll(
@@ -153,10 +155,7 @@ export class TrackService {
getFolderName(track: TrackEntity, playlist: PlaylistEntity): string { getFolderName(track: TrackEntity, playlist: PlaylistEntity): string {
return resolve( return resolve(
__dirname, this.utilsService.getPlaylistFolderPath(playlist.name),
'..',
this.configService.get<string>(EnviromentEnum.DOWNLOADS_PATH),
playlist.name,
this.getTrackFileName(track), this.getTrackFileName(track),
); );
} }