playlist subscription functionality
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {Body, Controller, Delete, Get, Param, Post} from '@nestjs/common';
|
||||
import {Body, Controller, Delete, Get, Param, Post, Put} from '@nestjs/common';
|
||||
import {PlaylistService} from "./playlist.service";
|
||||
import {PlaylistModel} from "./playlist.model";
|
||||
|
||||
@@ -20,6 +20,11 @@ export class PlaylistController {
|
||||
await this.service.create(playlist);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
update(@Param('id') id: number, @Body() playlist: Partial<PlaylistModel>): Promise<void> {
|
||||
return this.service.update(id, playlist);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: number): Promise<void> {
|
||||
return this.service.remove(id);
|
||||
|
||||
@@ -16,6 +16,9 @@ export class PlaylistEntity {
|
||||
@Column({ nullable: true })
|
||||
error?: string;
|
||||
|
||||
@Column({default: false})
|
||||
active?: boolean;
|
||||
|
||||
@Column({default: () => Date.now()})
|
||||
createdAt?: number;
|
||||
|
||||
|
||||
@@ -5,4 +5,5 @@ export interface PlaylistModel {
|
||||
tracks?: any[]; //todo fix it
|
||||
error?: string;
|
||||
createdAt?: number;
|
||||
active?: boolean;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {resolve} from "path";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {EnviromentEnum} from "../enviroment.enum";
|
||||
import * as fs from 'fs';
|
||||
import {Interval} from "@nestjs/schedule";
|
||||
const fetch = require('isomorphic-unfetch');
|
||||
const { getData, getPreview, getTracks, getDetails } = require('spotify-url-info')(fetch);
|
||||
|
||||
@@ -26,8 +27,8 @@ export class PlaylistService {
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<PlaylistEntity[]> {
|
||||
return this.repository.find({relations: {tracks: true}});
|
||||
findAll(relations: Record<string, boolean> = {tracks: true}, where?: Partial<PlaylistEntity>): Promise<PlaylistEntity[]> {
|
||||
return this.repository.find({where, relations});
|
||||
}
|
||||
|
||||
findOne(id: number): Promise<PlaylistEntity | null> {
|
||||
@@ -48,10 +49,8 @@ export class PlaylistService {
|
||||
} catch (err) {
|
||||
playlist2Save = {...playlist, error: String(err)};
|
||||
}
|
||||
const savedPlaylist = await this.repository.save(playlist2Save);
|
||||
this.io.emit('playlistNew', savedPlaylist);
|
||||
const folderName = resolve(__dirname, '..',this.configService.get<string>(EnviromentEnum.DOWNLOADS_PATH), savedPlaylist.name);
|
||||
!fs.existsSync(folderName) && fs.mkdirSync(folderName);
|
||||
const savedPlaylist = await this.save(playlist2Save);
|
||||
this.createPlaylistFolderStructure(savedPlaylist.name);
|
||||
for(let track of details?.tracks ?? []) {
|
||||
await this.trackService.create({
|
||||
artist: track.artist,
|
||||
@@ -61,9 +60,16 @@ export class PlaylistService {
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: number, playlist: PlaylistEntity): Promise<void> {
|
||||
async save(playlist: PlaylistEntity): Promise<PlaylistEntity> {
|
||||
const savedPlaylist = await this.repository.save(playlist);
|
||||
this.io.emit('playlistNew', savedPlaylist);
|
||||
return savedPlaylist;
|
||||
}
|
||||
|
||||
async update(id: number, playlist: Partial<PlaylistEntity>): Promise<void> {
|
||||
await this.repository.update(id, playlist);
|
||||
this.io.emit('trackPlaylist', playlist);
|
||||
const dbPlaylist = await this.findOne(id);
|
||||
this.io.emit('playlistUpdate', dbPlaylist);
|
||||
}
|
||||
|
||||
async retryFailedOfPlaylist(id: number): Promise<void> {
|
||||
@@ -74,4 +80,34 @@ export class PlaylistService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createPlaylistFolderStructure(playlistName): void {
|
||||
const folderName = resolve(__dirname, '..',this.configService.get<string>(EnviromentEnum.DOWNLOADS_PATH), playlistName);
|
||||
!fs.existsSync(folderName) && fs.mkdirSync(folderName);
|
||||
}
|
||||
|
||||
@Interval(3600000)
|
||||
async checkActivePlaylists(): Promise<void> {
|
||||
const activePlaylists = await this.findAll({}, {active: true});
|
||||
for (let playlist of activePlaylists) {
|
||||
let details;
|
||||
try {
|
||||
details = await getDetails(playlist.spotifyUrl);
|
||||
} catch (err) {
|
||||
await this.update(playlist.id, {...playlist, error: String(err)});
|
||||
}
|
||||
this.createPlaylistFolderStructure(playlist.name);
|
||||
for(let track of details?.tracks ?? []) {
|
||||
const track2Save = {
|
||||
artist: track.artist,
|
||||
song: track.name,
|
||||
spotifyUrl: track.previewUrl,
|
||||
}
|
||||
const isExist = !!(await this.trackService.findAll({...track2Save, playlist: {id: playlist.id}})).length;
|
||||
if(!isExist) {
|
||||
await this.trackService.create(track2Save, playlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ export class TrackService {
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
findAll(where?: Partial<TrackEntity>, relations: string[] = []): Promise<TrackEntity[]> {
|
||||
findAll(where?: {[key: string]: any}, relations: Record<string, boolean> = {}): Promise<TrackEntity[]> {
|
||||
return this.repository.find({where, relations});
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export class TrackService {
|
||||
|
||||
@Interval(1000)
|
||||
async download() {
|
||||
const queuedTracks = await this.findAll({status: TrackStatusEnum.Queued}, ['playlist']);
|
||||
const queuedTracks = await this.findAll({status: TrackStatusEnum.Queued}, {playlist: true});
|
||||
for (let track of queuedTracks) {
|
||||
await this.update(track.id, {...track, status: TrackStatusEnum.Downloading});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<i class="fa-solid is-clickable hover-icon"
|
||||
[ngClass]="playlist.active ? 'fa-lock' : 'fa-lock-open'"
|
||||
[title]="playlist.active ? 'Subscribe to playlist changes' : 'Unsubscribe from playlist changes'"
|
||||
(click)="setActive(playlist.id, playlist.active)"
|
||||
></i>
|
||||
<i class="fa-solid fa-repeat is-clickable hover-icon" title="Retry download failed tracks" (click)="retryFailed(playlist.id)"></i>
|
||||
<i class="fa-solid fa-xmark is-clickable hover-icon" title="Remove playlist from list" (click)="delete(playlist.id)"></i>
|
||||
<span *ngIf="!playlist.error" class="is-size-6 has-text-weight-medium">{{trackCompletedCount$ | async}}/{{trackCount$ | async}}</span>
|
||||
|
||||
@@ -57,4 +57,8 @@ export class PlaylistBoxComponent {
|
||||
retryFailed(id: number): void {
|
||||
this.service.retryFailed(id);
|
||||
}
|
||||
|
||||
setActive(id: number, currentActive: boolean = false): void {
|
||||
this.service.setActive(id, !currentActive)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface Playlist {
|
||||
name?: string;
|
||||
spotifyUrl: string;
|
||||
error?: string;
|
||||
active: boolean;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ export class PlaylistService {
|
||||
all$ = this.store.combine({
|
||||
entities: this.store.pipe(selectAllEntities()),
|
||||
UIEntities: this.store.pipe(selectEntities({ ref: UIEntitiesRef })),
|
||||
}).pipe(unionEntities(), map(data => data.sort((a, b) => b.createdAt - a.createdAt)));
|
||||
}).pipe(unionEntities(), map(data => data.sort((a, b) => this.sort(a, b))));
|
||||
|
||||
loading$ = this.store.pipe(joinRequestResult([STORE_NAME]));
|
||||
createLoading$ = this.store.pipe(joinRequestResult([CREATE_LOADING], { initialStatus: 'idle' }));
|
||||
@@ -100,7 +101,7 @@ export class PlaylistService {
|
||||
if (playlist?.error || errorCount === trackCount) {
|
||||
return PlaylistStatusEnum.Error;
|
||||
} else if (trackCount === completedCount) {
|
||||
return PlaylistStatusEnum.Completed;
|
||||
return playlist?.active ? PlaylistStatusEnum.Subscribed : PlaylistStatusEnum.Completed;
|
||||
} else if (errorCount > 1) {
|
||||
return PlaylistStatusEnum.Warning;
|
||||
}
|
||||
@@ -157,4 +158,12 @@ export class PlaylistService {
|
||||
retryFailed(id: number): void {
|
||||
this.http.get<void>(`${ENDPOINT}/retry/${id}`).subscribe();
|
||||
}
|
||||
|
||||
setActive(id: number, active: boolean): void {
|
||||
this.http.put<void>(`${ENDPOINT}/${id}`, {active}).subscribe();
|
||||
}
|
||||
|
||||
private sort(a: Playlist & PlaylistUi, b: Playlist & PlaylistUi): number {
|
||||
return a.active === b.active ? (b.createdAt - a.createdAt) : (a.active < b.active ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user