-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(playlist): implement playlist feature (#24)
- Loading branch information
Showing
76 changed files
with
3,584 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, CreateDateColumn } from "typeorm"; | ||
import { Field, ObjectType, Int } from "@nestjs/graphql"; | ||
|
||
@Entity({ name: "playlists" }) | ||
@ObjectType() | ||
export class Playlist extends BaseEntity { | ||
@Field(() => Int) | ||
@PrimaryGeneratedColumn() | ||
public id!: number; | ||
|
||
@Field(() => String) | ||
@Column({ type: "varchar", length: 255 }) | ||
public name!: string; | ||
|
||
@Field(() => [Int]) | ||
@Column({ type: "simple-array" }) | ||
public musicIds!: number[]; | ||
|
||
@Field(() => Date) | ||
@CreateDateColumn() | ||
public createdAt!: Date; | ||
|
||
@Field(() => Date) | ||
@CreateDateColumn() | ||
public updatedAt!: Date; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Module } from "@nestjs/common"; | ||
import { TypeOrmModule } from "@nestjs/typeorm"; | ||
|
||
import { PlaylistService } from "@playlist/playlist.service"; | ||
import { PlaylistResolver } from "@playlist/playlist.resolver"; | ||
|
||
import { MusicModule } from "@music/music.module"; | ||
|
||
import { Playlist } from "@playlist/models/playlist.model"; | ||
|
||
@Module({ | ||
imports: [TypeOrmModule.forFeature([Playlist]), MusicModule], | ||
providers: [PlaylistService, PlaylistResolver], | ||
exports: [PlaylistService], | ||
}) | ||
export class PlaylistModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { Test, TestingModule } from "@nestjs/testing"; | ||
import { PlaylistResolver } from "@playlist/playlist.resolver"; | ||
import { PlaylistEvents, PlaylistService } from "@playlist/playlist.service"; | ||
|
||
describe("PlaylistResolver", () => { | ||
let resolver: PlaylistResolver; | ||
let service: Record<string, jest.Mock>; | ||
|
||
beforeEach(async () => { | ||
service = { | ||
findById: jest.fn(), | ||
findAll: jest.fn(), | ||
createFromMusicIds: jest.fn(), | ||
addMusicsToPlaylist: jest.fn(), | ||
asyncIterator: jest.fn(), | ||
delete: jest.fn(), | ||
clear: jest.fn(), | ||
rename: jest.fn(), | ||
deleteItems: jest.fn(), | ||
}; | ||
|
||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [PlaylistResolver, { provide: PlaylistService, useValue: service }], | ||
}).compile(); | ||
|
||
resolver = module.get<PlaylistResolver>(PlaylistResolver); | ||
}); | ||
|
||
it("should be defined", () => { | ||
expect(resolver).toBeDefined(); | ||
}); | ||
|
||
it("should be able to find a playlist with given id", async () => { | ||
await resolver.playlist(1); | ||
expect(service.findById).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should be able to find all playlists", async () => { | ||
await resolver.playlists(); | ||
expect(service.findAll).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should be able to create a playlist", async () => { | ||
await resolver.createPlaylist("name", [1, 2, 3]); | ||
expect(service.createFromMusicIds).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should be able to add musics to a playlist", async () => { | ||
await resolver.addMusicsToPlaylist(1, [1, 2, 3]); | ||
expect(service.addMusicsToPlaylist).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should be able to clear a playlist", async () => { | ||
await resolver.clearPlaylist(1); | ||
expect(service.clear).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should be able to rename a playlist", async () => { | ||
await resolver.renamePlaylist(1, "name"); | ||
expect(service.rename).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should be able to delete a playlist", async () => { | ||
await resolver.deletePlaylist(1); | ||
expect(service.delete).toHaveBeenCalled(); | ||
}); | ||
|
||
it("should be able to delete items from a playlist", async () => { | ||
await resolver.deletePlaylistItems(1, [1, 2, 3]); | ||
expect(service.deleteItems).toHaveBeenCalledWith(1, [1, 2, 3]); | ||
}); | ||
|
||
it("should be able to resolve musics", async () => { | ||
const musicLoader = { load: jest.fn() }; | ||
await resolver.musics({ musicIds: [1, 2, 3] } as any, { music: musicLoader } as any); | ||
|
||
expect(musicLoader.load).toHaveBeenCalledTimes(3); | ||
}); | ||
|
||
it("should be able to subscribe to playlistCreated event", async () => { | ||
await resolver.playlistCreated(); | ||
expect(service.asyncIterator).toHaveBeenCalledWith(PlaylistEvents.CREATED); | ||
}); | ||
|
||
it("should be able to subscribe to playlistDeleted event", async () => { | ||
await resolver.playlistDeleted(); | ||
expect(service.asyncIterator).toHaveBeenCalledWith(PlaylistEvents.DELETED); | ||
}); | ||
|
||
it("should be able to subscribe to playlistUpdated event", async () => { | ||
await resolver.playlistUpdated(); | ||
expect(service.asyncIterator).toHaveBeenCalledWith(PlaylistEvents.UPDATED); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { Inject } from "@nestjs/common"; | ||
import { Args, Context, Int, Mutation, Parent, Query, ResolveField, Resolver, Subscription } from "@nestjs/graphql"; | ||
|
||
import { PlaylistEvents, PlaylistService } from "@playlist/playlist.service"; | ||
import { Playlist } from "@playlist/models/playlist.model"; | ||
import { Music } from "@music/models/music.model"; | ||
|
||
import { GraphQLContext } from "@root/context"; | ||
|
||
@Resolver(() => Playlist) | ||
export class PlaylistResolver { | ||
public constructor(@Inject(PlaylistService) private readonly playlistService: PlaylistService) {} | ||
|
||
@Query(() => Playlist, { nullable: true }) | ||
public async playlist(@Args("id", { type: () => Int }) id: number) { | ||
return this.playlistService.findById(id); | ||
} | ||
|
||
@Query(() => [Playlist]) | ||
public async playlists(): Promise<Playlist[]> { | ||
return this.playlistService.findAll(); | ||
} | ||
|
||
@Mutation(() => Playlist) | ||
public async createPlaylist( | ||
@Args("name", { type: () => String }) name: string, | ||
@Args("musicIds", { type: () => [Int] }) musicIds: number[], | ||
): Promise<Playlist> { | ||
return this.playlistService.createFromMusicIds(name, musicIds); | ||
} | ||
|
||
@Mutation(() => Boolean) | ||
public async clearPlaylist(@Args("playlistId", { type: () => Int }) playlistId: number): Promise<boolean> { | ||
await this.playlistService.clear(playlistId); | ||
return true; | ||
} | ||
|
||
@Mutation(() => Boolean) | ||
public async deletePlaylist(@Args("id", { type: () => Int }) id: number): Promise<boolean> { | ||
await this.playlistService.delete(id); | ||
return true; | ||
} | ||
|
||
@Mutation(() => Boolean) | ||
public async deletePlaylistItems( | ||
@Args("playlistId", { type: () => Int }) playlistId: number, | ||
@Args("indices", { type: () => [Int] }) indices: number[], | ||
): Promise<boolean> { | ||
await this.playlistService.deleteItems(playlistId, indices); | ||
return true; | ||
} | ||
|
||
@Mutation(() => Boolean) | ||
public async renamePlaylist( | ||
@Args("id", { type: () => Int }) id: number, | ||
@Args("name", { type: () => String }) name: string, | ||
): Promise<boolean> { | ||
await this.playlistService.rename(id, name); | ||
return true; | ||
} | ||
|
||
@Mutation(() => Boolean) | ||
public async addMusicsToPlaylist( | ||
@Args("playlistId", { type: () => Int }) playlistId: number, | ||
@Args("musicIds", { type: () => [Int] }) musicIds: number[], | ||
): Promise<boolean> { | ||
await this.playlistService.addMusicsToPlaylist(playlistId, musicIds); | ||
return true; | ||
} | ||
|
||
@ResolveField(() => [Music]) | ||
public async musics( | ||
@Parent() playlist: Playlist, | ||
@Context("loaders") loaders: GraphQLContext["loaders"], | ||
): Promise<Music[]> { | ||
return Promise.all(playlist.musicIds.map(id => loaders.music.load(id))); | ||
} | ||
|
||
@Subscription(() => Playlist, { resolve: payload => payload[PlaylistEvents.CREATED] }) | ||
public playlistCreated() { | ||
return this.playlistService.asyncIterator(PlaylistEvents.CREATED); | ||
} | ||
|
||
@Subscription(() => Int, { resolve: payload => payload[PlaylistEvents.DELETED] }) | ||
public playlistDeleted() { | ||
return this.playlistService.asyncIterator(PlaylistEvents.DELETED); | ||
} | ||
|
||
@Subscription(() => Playlist, { resolve: payload => payload[PlaylistEvents.UPDATED] }) | ||
public playlistUpdated() { | ||
return this.playlistService.asyncIterator(PlaylistEvents.UPDATED); | ||
} | ||
} |
Oops, something went wrong.