Skip to content

Commit

Permalink
feat: get playlists
Browse files Browse the repository at this point in the history
  • Loading branch information
sumitkolhe committed Mar 16, 2024
1 parent 7bbe2ea commit cda535b
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/common/constants/endpoint.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export const Endpoints = {
albums: 'artist.getArtistMoreAlbum'
},
playlists: {
id: 'playlist.getDetails'
id: 'playlist.getDetails',
link: 'webapi.get'
},
modules: 'content.getBrowseModules',
trending: 'content.getTrending'
Expand Down
1 change: 1 addition & 0 deletions src/modules/playlists/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './playlist.controller'
84 changes: 84 additions & 0 deletions src/modules/playlists/controllers/playlist.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import { AlbumModel } from '#modules/albums/models'
import { PlaylistService } from '#modules/playlists/services'

export class PlaylistController {
public controller: OpenAPIHono
private playlistService: PlaylistService

constructor() {
this.controller = new OpenAPIHono()
this.playlistService = new PlaylistService()
}

public initRoutes() {
this.controller.openapi(
createRoute({
method: 'get',
path: '/playlists',
tags: ['Playlist'],
summary: 'Retrieve a playlist by ID or link',
description: 'Retrieve a playlist by providing either an ID or a direct link to the playlist on JioSaavn.',
operationId: 'getPlaylistByIdOrLink',
request: {
query: z.object({
id: z.string().optional().openapi({
title: 'Playlist ID',
description: 'The unique ID of the playlist',
type: 'string',
example: '82914609',
default: '82914609'
}),
link: z
.string()
.url()
.optional()
.transform((value) => value?.match(/jiosaavn\.com\/featured\/[^/]+\/([^/]+)$/)?.[1])
.openapi({
title: 'Playlist Link',
description: 'A direct link to the playlist on JioSaavn',
type: 'string',
example: 'https://www.jiosaavn.com/featured/its-indie-english/AMoxtXyKHoU_',
default: 'https://www.jiosaavn.com/featured/its-indie-english/AMoxtXyKHoU_'
})
})
},
responses: {
200: {
description: 'Successful response with playlist details',
content: {
'application/json': {
schema: z.object({
success: z.boolean().openapi({
description: 'Indicates the success status of the request.',
type: 'boolean',
example: true
}),
data: AlbumModel.openapi({
title: 'Album Details',
description: 'The detailed information of the playlist.'
})
})
}
}
},
400: { description: 'Bad request due to missing or invalid query parameters.' },
404: { description: 'The playlist could not be found with the provided ID or link.' }
}
}),
async (ctx) => {
const { id, link } = ctx.req.valid('query')

if (!link && !id) {
return ctx.json({ success: false, message: 'Either playlist ID or link is required' }, 400)
}

const response = link
? await this.playlistService.getPlaylistByLink(link)
: await this.playlistService.getPlaylistById(id!)

return ctx.json({ success: true, data: response })
}
)
}
}
1 change: 1 addition & 0 deletions src/modules/playlists/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './playlist.helper'
22 changes: 22 additions & 0 deletions src/modules/playlists/helpers/playlist.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { z } from 'zod'
import type { PlaylistAPIResponseModel, PlaylistModel } from '#modules/playlists/models'
import { createArtistMap, createSongPayload } from '#modules/songs/helpers'
import { createImageLinks } from '#common/helpers'

export const createPlaylistPayload = (
album: z.infer<typeof PlaylistAPIResponseModel>
): z.infer<typeof PlaylistModel> => ({
id: album.id,
name: album.title,
description: album.header_desc,
type: album.type,
year: Number(album.year || 0),
playCount: Number(album.play_count),
language: album.language,
explicitContent: album.explicit_content === '1',
url: album.perma_url,
songCount: Number(album.list_count || 0),
artists: album.more_info.artists?.map(createArtistMap),
image: createImageLinks(album.image),
...(album.list && { songs: album.list.map((song) => createSongPayload(song)) })
})
1 change: 1 addition & 0 deletions src/modules/playlists/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './playlist.model'
66 changes: 66 additions & 0 deletions src/modules/playlists/models/playlist.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { z } from 'zod'
import { DownloadLinkModel } from '#common/models'
import { SongAPIResponseModel, SongArtistMapModel, SongModel } from '#modules/songs/models'

export const PlaylistAPIResponseModel = z.object({
id: z.string(),
title: z.string(),
subtitle: z.string(),
header_desc: z.string(),
type: z.string(),
perma_url: z.string(),
image: z.string(),
language: z.string(),
year: z.string(),
play_count: z.string(),
explicit_content: z.string(),
list_count: z.string(),
list_type: z.string(),
list: z.array(SongAPIResponseModel),
more_info: z.object({
uid: z.string(),
is_dolby_content: z.boolean(),
subtype: z.array(z.string()).default([]),
last_updated: z.string(),
username: z.string(),
firstname: z.string(),
lastname: z.string(),
is_followed: z.string(),
isFY: z.boolean(),
follower_count: z.string(),
fan_count: z.string(),
playlist_type: z.string(),
share: z.string(),
sub_types: z.array(z.string()),
images: z.array(z.string()),
H2: z.string().nullable(),
subheading: z.string(),
video_count: z.string(),
artists: z.array(
z.object({
id: z.string(),
name: z.string(),
role: z.string(),
image: z.string(),
type: z.string(),
perma_url: z.string()
})
)
})
})

export const PlaylistModel = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
year: z.number(),
type: z.string(),
playCount: z.number(),
language: z.string(),
explicitContent: z.boolean(),
songCount: z.number(),
url: z.string(),
image: z.array(DownloadLinkModel),
songs: z.array(SongModel),
artists: z.array(SongArtistMapModel)
})
1 change: 1 addition & 0 deletions src/modules/playlists/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './playlist.service'
19 changes: 19 additions & 0 deletions src/modules/playlists/services/playlist.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GetPlaylistByIdUseCase, GetPlaylistByLinkUseCase } from '#modules/playlists/use-cases'

export class PlaylistService {
private readonly getPlaylistByIdUseCase: GetPlaylistByIdUseCase
private readonly getPlaylistByLinkUseCase: GetPlaylistByLinkUseCase

constructor() {
this.getPlaylistByIdUseCase = new GetPlaylistByIdUseCase()
this.getPlaylistByLinkUseCase = new GetPlaylistByLinkUseCase()
}

getPlaylistById = (playlistId: string) => {
return this.getPlaylistByIdUseCase.execute(playlistId)
}

getPlaylistByLink = (token: string) => {
return this.getPlaylistByLinkUseCase.execute(token)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { HTTPException } from 'hono/http-exception'
import type { z } from 'zod'
import type { IUseCase } from '#common/types'
import type { PlaylistAPIResponseModel, PlaylistModel } from '#modules/playlists/models'
import { useFetch } from '#common/helpers'
import { Endpoints } from '#common/constants'
import { createPlaylistPayload } from '#modules/playlists/helpers'

export class GetPlaylistByIdUseCase implements IUseCase<string, z.infer<typeof PlaylistModel>> {
constructor() {}

async execute(id: string) {
const response = await useFetch<z.infer<typeof PlaylistAPIResponseModel>>(Endpoints.playlists.id, { listid: id })

if (!response) throw new HTTPException(404, { message: 'playlist not found' })

return createPlaylistPayload(response)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './get-playlist-by-id.use-case'
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { HTTPException } from 'hono/http-exception'
import type { z } from 'zod'
import type { IUseCase } from '#common/types'
import type { PlaylistAPIResponseModel, PlaylistModel } from '#modules/playlists/models'
import { useFetch } from '#common/helpers'
import { Endpoints } from '#common/constants'
import { createPlaylistPayload } from '#modules/playlists/helpers'

export class GetPlaylistByLinkUseCase implements IUseCase<string, z.infer<typeof PlaylistModel>> {
constructor() {}

async execute(token: string) {
const response = await useFetch<z.infer<typeof PlaylistAPIResponseModel>>(Endpoints.albums.link, {
token,
type: 'playlist'
})

if (!response) throw new HTTPException(404, { message: 'playlist not found' })

return createPlaylistPayload(response)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './get-playlist-by-link.use-case'
2 changes: 2 additions & 0 deletions src/modules/playlists/use-cases/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './get-playlist-by-id'
export * from './get-playlist-by-link'
4 changes: 3 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { App } from './app'
import { AlbumController, ArtistController, SearchController, SongController } from '#modules/index'
import { PlaylistController } from '#modules/playlists/controllers'

const app = new App([
new SearchController(),
new SongController(),
new AlbumController(),
new ArtistController()
new ArtistController(),
new PlaylistController()
]).getApp()

export default app

0 comments on commit cda535b

Please sign in to comment.