feat(subscriptions): gestion des abonnements par groupes façon PocketTube
CI / build-and-test (push) Failing after 8m18s
CI / build-and-test (push) Failing after 8m18s
- Tables subscription_groups + subscription_group_members (migration + schema) - API CRUD groupes, membres par groupe, groupes par abonnement - Page Abonnements: onglets Groupes/Chaines, modale creation/edition (icone/couleur), assignation unitaire + lot, filtre par groupe + recherche - Sidebar: section Groupes avec navigation directe (queryParams) - i18n nav.groups FR/EN + 4 tests unitaires service
This commit is contained in:
@@ -66,6 +66,13 @@ import {
|
||||
subscribeChannel,
|
||||
unsubscribeChannel,
|
||||
isSubscribed,
|
||||
listSubscriptionGroups,
|
||||
createSubscriptionGroup,
|
||||
updateSubscriptionGroup,
|
||||
deleteSubscriptionGroup,
|
||||
setSubscriptionGroupMembers,
|
||||
setSubscriptionGroups,
|
||||
listSubscriptionGroupMembersByUser,
|
||||
insertDownloadJob,
|
||||
getDownloadJob,
|
||||
listDownloadJobs,
|
||||
@@ -334,6 +341,94 @@ r.delete('/subscriptions/:subscriptionId', authMiddlewareCookieAware, subscripti
|
||||
}
|
||||
});
|
||||
|
||||
// -------------------- Subscription groups APIs (façon PocketTube) --------------------
|
||||
|
||||
r.get('/subscription-groups', authMiddlewareCookieAware, subscriptionsLimiter, (req, res) => {
|
||||
try {
|
||||
const groups = listSubscriptionGroups(req.user.id);
|
||||
const members = listSubscriptionGroupMembersByUser(req.user.id);
|
||||
return res.json({ groups, members });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: 'groups_list_failed', details: String(error?.message || error) });
|
||||
}
|
||||
});
|
||||
|
||||
r.post('/subscription-groups', authMiddlewareCookieAware, subscriptionsLimiter, (req, res) => {
|
||||
try {
|
||||
const group = createSubscriptionGroup({
|
||||
userId: req.user.id,
|
||||
name: req.body?.name,
|
||||
color: req.body?.color,
|
||||
icon: req.body?.icon,
|
||||
});
|
||||
return res.status(201).json(group);
|
||||
} catch (error) {
|
||||
const msg = error?.message || 'group_create_failed';
|
||||
if (msg === 'group_name_required') return res.status(400).json({ error: msg });
|
||||
if (msg === 'group_name_taken') return res.status(409).json({ error: msg });
|
||||
return res.status(500).json({ error: 'group_create_failed' });
|
||||
}
|
||||
});
|
||||
|
||||
r.patch('/subscription-groups/:groupId', authMiddlewareCookieAware, subscriptionsLimiter, (req, res) => {
|
||||
try {
|
||||
const group = updateSubscriptionGroup({
|
||||
userId: req.user.id,
|
||||
groupId: req.params.groupId,
|
||||
name: req.body?.name,
|
||||
color: req.body?.color,
|
||||
icon: req.body?.icon,
|
||||
});
|
||||
if (!group) return res.status(404).json({ error: 'not_found' });
|
||||
return res.json(group);
|
||||
} catch (error) {
|
||||
const msg = error?.message || 'group_update_failed';
|
||||
if (msg === 'group_name_required') return res.status(400).json({ error: msg });
|
||||
if (msg === 'group_name_taken') return res.status(409).json({ error: msg });
|
||||
return res.status(500).json({ error: 'group_update_failed' });
|
||||
}
|
||||
});
|
||||
|
||||
r.delete('/subscription-groups/:groupId', authMiddlewareCookieAware, subscriptionsLimiter, (req, res) => {
|
||||
try {
|
||||
const info = deleteSubscriptionGroup({ userId: req.user.id, groupId: req.params.groupId });
|
||||
if ((info?.changes || 0) === 0) return res.status(404).json({ error: 'not_found' });
|
||||
return res.status(204).end();
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: 'group_delete_failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Remplace les chaînes d'un groupe : { subscriptionIds: number[] }
|
||||
r.put('/subscription-groups/:groupId/members', authMiddlewareCookieAware, subscriptionsLimiter, (req, res) => {
|
||||
try {
|
||||
const group = setSubscriptionGroupMembers({
|
||||
userId: req.user.id,
|
||||
groupId: req.params.groupId,
|
||||
subscriptionIds: req.body?.subscriptionIds,
|
||||
});
|
||||
if (!group) return res.status(404).json({ error: 'not_found' });
|
||||
return res.json(group);
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: 'group_members_failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Remplace les groupes d'un abonnement : { groupIds: string[] }
|
||||
r.put('/subscriptions/:subscriptionId/groups', authMiddlewareCookieAware, subscriptionsLimiter, (req, res) => {
|
||||
try {
|
||||
const groupIds = setSubscriptionGroups({
|
||||
userId: req.user.id,
|
||||
subscriptionId: Number(req.params.subscriptionId),
|
||||
groupIds: req.body?.groupIds,
|
||||
});
|
||||
if (!groupIds) return res.status(404).json({ error: 'not_found' });
|
||||
return res.json({ subscriptionId: Number(req.params.subscriptionId), groupIds });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error: 'subscription_groups_failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Public: view a playlist if allowed (owner or public). Authorization header is optional.
|
||||
r.get('/playlists/:id/view', (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user