Skip to content

Commit 804ce12

Browse files
committed
feat: add endpoints for retrieving playlist resources by queue position and shuffle
1 parent b025b04 commit 804ce12

File tree

3 files changed

+113
-5
lines changed

3 files changed

+113
-5
lines changed

src/controllers/playlist/playlist.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,11 @@ export const verifyPrivatePlaylistOwnershipIfNeeded = () => {
112112
if (!playlist) {
113113
return res.status(404).json({ message: 'Playlist not found' });
114114
}
115+
116+
const isOwner = !!account?.id && playlist.account.id === account.id;
115117

116118
if (playlist.sharable_status.id === SharableStatusEnum.Private) {
117-
if (!account?.id || playlist.account.id !== account.id) {
119+
if (!isOwner) {
118120
return res.status(404).json({ message: 'Playlist not found' });
119121
}
120122
}

src/controllers/playlist/playlistResource.ts

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Request, Response } from 'express';
22
import Joi from 'joi';
3-
import { PlaylistResourceService } from 'podverse-orm';
3+
import { PlaylistResourceIdTextOptions } from 'podverse-helpers';
4+
import { FindManyOptions, PlaylistResource, PlaylistResourceService } from 'podverse-orm';
45
import { handleGenericErrorResponse } from '../helpers/error';
5-
import { validateParamsObject } from '@api/lib/validation';
6+
import { validateParamsObject, validateQueryObject } from '@api/lib/validation';
67
import { verifyPlaylistOwnership, verifyPrivatePlaylistOwnershipIfNeeded } from './playlist';
78
import { ensureAuthenticated, optionalEnsureAuthenticated } from '@api/lib/auth';
89
import { getPaginationParams } from '../helpers/pagination';
@@ -11,6 +12,26 @@ const playlistIdSchema = Joi.object({
1112
playlist_id_text: Joi.string().required()
1213
});
1314

15+
const getManyForQueueByListPositionParamsSchema = Joi.object({
16+
playlist_id_text: Joi.string().required()
17+
});
18+
19+
const getManyForQueueByListPositionQuerySchema = Joi.object({
20+
item_id_text: Joi.string().optional(),
21+
clip_id_text: Joi.string().optional(),
22+
item_soundbite_id_text: Joi.string().optional(),
23+
direction: Joi.string().valid('forward', 'backward').required()
24+
});
25+
26+
const getManyByPlaylistShuffleParamsSchema = Joi.object({
27+
playlist_id_text: Joi.string().required()
28+
});
29+
30+
const getManyByPlaylistShuffleQuerySchema = Joi.object({
31+
shuffleHash: Joi.string().required(),
32+
page: Joi.number().integer().min(1).default(1)
33+
});
34+
1435
class PlaylistResourceController {
1536
private static playlistResourceService = new PlaylistResourceService();
1637

@@ -19,9 +40,13 @@ class PlaylistResourceController {
1940
ensureAuthenticated(req, res, async () => {
2041
verifyPlaylistOwnership()(req, res, async () => {
2142
const { playlist_id_text } = req.params;
43+
const account_id = req.user!.id;
2244

2345
try {
24-
const playlistResources = await PlaylistResourceController.playlistResourceService.getAllByPlaylistIdText(playlist_id_text);
46+
const playlistResources = await PlaylistResourceController.playlistResourceService.getAllByPlaylistIdText(
47+
playlist_id_text,
48+
account_id
49+
);
2550
res.status(200).json(playlistResources);
2651
} catch (err) {
2752
handleGenericErrorResponse(res, err);
@@ -31,17 +56,96 @@ class PlaylistResourceController {
3156
});
3257
}
3358

59+
static async getManyForQueueByListPosition(req: Request, res: Response): Promise<void> {
60+
validateParamsObject(getManyForQueueByListPositionParamsSchema, req, res, async () => {
61+
validateQueryObject(getManyForQueueByListPositionQuerySchema, req, res, async () => {
62+
optionalEnsureAuthenticated(req, res, async () => {
63+
const { playlist_id_text } = req.params;
64+
const {
65+
item_id_text,
66+
clip_id_text,
67+
item_soundbite_id_text,
68+
direction
69+
} = req.query as {
70+
item_id_text?: PlaylistResourceIdTextOptions['item_id_text'];
71+
clip_id_text?: PlaylistResourceIdTextOptions['clip_id_text'];
72+
item_soundbite_id_text?: PlaylistResourceIdTextOptions['item_soundbite_id_text'];
73+
direction: 'forward' | 'backward';
74+
};
75+
const account_id = req.user?.id || null;
76+
77+
if (!item_id_text && !clip_id_text && !item_soundbite_id_text) {
78+
res.status(400).json({ message: 'One of item_id_text, clip_id_text, or item_soundbite_id_text must be provided' });
79+
return;
80+
}
81+
82+
try {
83+
const playlistResources = await PlaylistResourceController
84+
.playlistResourceService
85+
.getManyForQueueByListPosition(
86+
playlist_id_text,
87+
{ item_id_text, clip_id_text, item_soundbite_id_text },
88+
direction,
89+
account_id
90+
);
91+
res.json({ data: playlistResources });
92+
} catch (err) {
93+
handleGenericErrorResponse(res, err);
94+
}
95+
});
96+
});
97+
});
98+
}
99+
100+
static async getManyByPlaylistShuffle(req: Request, res: Response): Promise<void> {
101+
validateParamsObject(getManyByPlaylistShuffleParamsSchema, req, res, async () => {
102+
validateQueryObject(getManyByPlaylistShuffleQuerySchema, req, res, async () => {
103+
optionalEnsureAuthenticated(req, res, async () => {
104+
verifyPrivatePlaylistOwnershipIfNeeded()(req, res, async () => {
105+
const { playlist_id_text } = req.params;
106+
const { page, limit, offset } = getPaginationParams(req);
107+
const { shuffleHash } = req.query as { shuffleHash: string };
108+
const account_id = req.user?.id || null;
109+
110+
try {
111+
const config: FindManyOptions<PlaylistResource> = {
112+
skip: offset,
113+
take: limit
114+
};
115+
const playlistResources = await PlaylistResourceController.playlistResourceService.getManyByPlaylistShuffle(
116+
playlist_id_text,
117+
shuffleHash,
118+
account_id,
119+
config
120+
);
121+
const totalCount = await PlaylistResourceController.playlistResourceService.getAllByPlaylistIdTextCount(playlist_id_text);
122+
123+
res.status(200).json({
124+
data: playlistResources,
125+
meta: { page, count: totalCount, limit }
126+
});
127+
} catch (err) {
128+
handleGenericErrorResponse(res, err);
129+
}
130+
});
131+
});
132+
});
133+
});
134+
}
135+
34136
static async getManyByPlaylistIdText(req: Request, res: Response): Promise<void> {
35137
validateParamsObject(playlistIdSchema, req, res, async () => {
36138
optionalEnsureAuthenticated(req, res, async () => {
37139
verifyPrivatePlaylistOwnershipIfNeeded()(req, res, async () => {
38140
const { playlist_id_text } = req.params;
39141
const { page, limit, offset } = getPaginationParams(req);
40-
142+
const account_id = req.user?.id || null;
143+
41144
try {
42145
const playlistResources = await PlaylistResourceController
43146
.playlistResourceService.getManyByPlaylistIdText(
44147
playlist_id_text,
148+
account_id,
45149
{
46150
skip: offset,
47151
take: limit

src/routes/playlist.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ router.get('/public/top', asyncHandler(PlaylistController.getManyPublicTop));
2828
router.post('/', asyncHandler(PlaylistController.createPlaylist));
2929

3030
router.get('/:playlist_id_text/resources/private-all', asyncHandler(PlaylistResourceController.getAllByPlaylistIdTextPrivate));
31+
router.get('/:playlist_id_text/resources/queue-by-list-position', asyncHandler(PlaylistResourceController.getManyForQueueByListPosition));
32+
router.get('/:playlist_id_text/resources/shuffle', asyncHandler(PlaylistResourceController.getManyByPlaylistShuffle));
3133
router.get('/:playlist_id_text/resources', asyncHandler(PlaylistResourceController.getManyByPlaylistIdText));
3234
router.get('/:playlist_id_text', asyncHandler(PlaylistController.getPlaylistById));
3335
router.patch('/:playlist_id_text', asyncHandler(PlaylistController.updatePlaylist));

0 commit comments

Comments
 (0)