@saooti/octopus-sdk 41.10.9 → 41.11.0-beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,62 @@
1
+ import { Mix } from "../../stores/class/radio/mix";
1
2
  import { useAuthStore } from "../../stores/AuthStore";
2
3
  import type { Emission } from "../../stores/class/general/emission";
3
4
  import type { Podcast } from "../../stores/class/general/podcast";
5
+ import { PlaylistMedia } from "../../stores/class/radio/playlistMedia";
6
+ import { Cartouchier } from "../../stores/class/cartouchier/cartouchier";
7
+ import { Media } from "../../stores/class/general/media";
4
8
 
5
9
  type Role =
6
10
  'ADMIN'|'ORGANISATION'|
7
11
  'PRODUCTION'|'RESTRICTED_PRODUCTION'|'PODCAST_CRUD'|'PODCAST_VALIDATION'|
8
- 'PLAYLISTS'|'RESTRICTED_ANIMATION';
12
+ 'PLAYLISTS'|'ANIMATION'|'RESTRICTED_ANIMATION'|'RADIO'|'LIVE';
9
13
 
10
- export enum EditRight {
11
- None, // User cannot edit
12
- Restricted, // User cannot edit because element is used elsewhere
13
- Full // User can edit
14
+ export enum ActionRight {
15
+ Allowed = 'allowed', // User can perform the action
16
+ DeniedNoRight = 'no_right', // User lacks the required role
17
+ DeniedNotOwner = 'not_owner' // User has a restricted role but does not own the resource
18
+ }
19
+
20
+ // Constraint type for the object passed to deriveCanFunctions.
21
+ // any[] is intentional: it allows functions with any parameter signature to satisfy
22
+ // the constraint while still enforcing that the return type is ActionRight.
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ type SyncRightFns = Record<string, (...args: any[]) => ActionRight>;
25
+
26
+ // Maps every key of the form `get${A}Right` to `can${A}`, preserving the parameter
27
+ // types of the original function but changing the return type to boolean.
28
+ // Keys that do not match the naming convention are dropped (mapped to never).
29
+ type CanFns<T extends SyncRightFns> = {
30
+ [K in keyof T as K extends `get${infer A}Right` ? `can${A}` : never]:
31
+ T[K] extends (...args: infer P) => ActionRight ? (...args: P) => boolean : never;
32
+ };
33
+
34
+ /**
35
+ * Generates boolean shortcut functions from a set of `getRightXxx` functions.
36
+ *
37
+ * Convention: `get${Action}Right(…args) → ActionRight`
38
+ * becomes `can${Action}(…args) → boolean`
39
+ *
40
+ * The generated function returns true when the underlying getRightXxx returns
41
+ * ActionRight.Allowed, and false otherwise. This means callers that only need
42
+ * a simple yes/no answer can use canXxx(), while callers that need the specific
43
+ * denial reason (DeniedNoRight vs DeniedNotOwner) can call getRightXxx() directly.
44
+ *
45
+ * Only synchronous functions should be passed here; async rights functions must
46
+ * be wrapped manually.
47
+ */
48
+ function deriveCanFunctions<T extends SyncRightFns>(fns: T): CanFns<T> {
49
+ const result = {} as CanFns<T>;
50
+ for (const key of Object.keys(fns) as (keyof T & string)[]) {
51
+ if (key.startsWith('get') && key.endsWith('Right')) {
52
+ const canKey = `can${key.slice(3, -5)}` as keyof CanFns<T>;
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ (result as any)[canKey] = (...args: unknown[]) =>
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ (fns as any)[key](...args) === ActionRight.Allowed;
57
+ }
58
+ }
59
+ return result;
14
60
  }
15
61
 
16
62
  /**
@@ -25,225 +71,351 @@ export const useRights = () => {
25
71
  return (authStore.authRole as Role[]).findIndex((r: Role) => roles.includes(r)) > -1;
26
72
  }
27
73
 
28
- // Creation is limited by roles
29
- function canCreateEmission(): boolean {
30
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION');
74
+ // Emission rights
75
+ function getCreateEmissionRight(): ActionRight {
76
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION')
77
+ ? ActionRight.Allowed
78
+ : ActionRight.DeniedNoRight;
31
79
  }
32
80
 
33
- function canEditEmission(emission: Emission): boolean {
81
+ function getEditEmissionRight(emission: Emission): ActionRight {
34
82
  if (
35
- // Can edit new emissions
36
- (!emission.emissionId && canCreateEmission()) ||
37
- // Can edit when with sufficient rights
38
- roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')
83
+ (!emission.emissionId && getCreateEmissionRight() === ActionRight.Allowed) ||
84
+ roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')
39
85
  ) {
40
- return true;
86
+ return ActionRight.Allowed;
41
87
  }
42
-
43
- // Can only edit if has created the emission
44
- return (roleContainsAny('RESTRICTED_PRODUCTION') && emission.createdByUserId === authStore.authProfile?.userId);
88
+ if (roleContainsAny('RESTRICTED_PRODUCTION')) {
89
+ return emission.createdByUserId === authStore.authProfile?.userId
90
+ ? ActionRight.Allowed
91
+ : ActionRight.DeniedNotOwner;
92
+ }
93
+ return ActionRight.DeniedNoRight;
45
94
  }
46
95
 
47
- function canDeleteEmission(): boolean {
48
- // In case of restricted production, it will only delete podcasts
49
- // created by user, and delete the emission only if empty afterwards
50
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION');
96
+ function getDeleteEmissionRight(): ActionRight {
97
+ // In case of restricted production, it will only delete podcasts
98
+ // created by user, and delete the emission only if empty afterwards
99
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION')
100
+ ? ActionRight.Allowed
101
+ : ActionRight.DeniedNoRight;
51
102
  }
52
103
 
53
- function canEditCommentsConfigEmission(): boolean {
54
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
104
+ function getEditCommentsConfigEmissionRight(): ActionRight {
105
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')
106
+ ? ActionRight.Allowed
107
+ : ActionRight.DeniedNoRight;
55
108
  }
56
109
 
57
- function canCreatePodcast(): boolean {
58
- // All roles that can create podcasts
110
+ // Podcast rights
111
+ function getCreatePodcastRight(): ActionRight {
59
112
  return roleContainsAny(
60
- 'ADMIN',
61
- 'ORGANISATION',
62
- 'PRODUCTION',
63
- 'PODCAST_CRUD',
64
- 'RESTRICTED_PRODUCTION',
65
- 'RESTRICTED_ANIMATION'
66
- );
67
- }
68
-
69
- function canDuplicatePodcast(): boolean {
70
- // Same as creation but notably without PODCAST_CRUD and
71
- // RESTRICTED_ANIMATION
72
- return roleContainsAny(
73
- 'ADMIN',
74
- 'ORGANISATION',
75
- 'PRODUCTION',
76
- 'RESTRICTED_PRODUCTION',
77
- );
113
+ 'ADMIN', 'ORGANISATION', 'PRODUCTION', 'PODCAST_CRUD',
114
+ 'RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION'
115
+ )
116
+ ? ActionRight.Allowed
117
+ : ActionRight.DeniedNoRight;
118
+ }
119
+
120
+ function getDuplicatePodcastRight(): ActionRight {
121
+ // Same as creation but notably without PODCAST_CRUD and RESTRICTED_ANIMATION
122
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION')
123
+ ? ActionRight.Allowed
124
+ : ActionRight.DeniedNoRight;
78
125
  }
79
126
 
80
- function canEditPodcast(podcast: Podcast): boolean {
81
- // Full rights users can edit any podcast
127
+ function getEditPodcastRight(podcast: Podcast): ActionRight {
82
128
  if (roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')) {
83
- return true;
129
+ return ActionRight.Allowed;
84
130
  }
85
-
86
- // RESTRICTED users can only edit their own podcasts
87
131
  if (roleContainsAny('RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION')) {
88
- return podcast.createdByUserId === authStore.authProfile?.userId;
132
+ return podcast.createdByUserId === authStore.authProfile?.userId
133
+ ? ActionRight.Allowed
134
+ : ActionRight.DeniedNotOwner;
89
135
  }
90
-
91
- // PODCAST_CRUD can only edit their own non-valid podcasts
92
136
  if (roleContainsAny('PODCAST_CRUD')) {
93
- return podcast.valid === false &&
94
- podcast.publisher?.userId === authStore.authProfile?.userId;
137
+ return podcast.valid === false && podcast.publisher?.userId === authStore.authProfile?.userId
138
+ ? ActionRight.Allowed
139
+ : ActionRight.DeniedNotOwner;
95
140
  }
96
-
97
- return false;
141
+ return ActionRight.DeniedNoRight;
98
142
  }
99
143
 
100
- function canDeletePodcast(podcast: Podcast): boolean {
101
- // Same permissions as editing
102
- return canEditPodcast(podcast);
144
+ function getDeletePodcastRight(podcast: Podcast): ActionRight {
145
+ return getEditPodcastRight(podcast);
103
146
  }
104
147
 
105
- function canValidatePodcast(): boolean {
106
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'PODCAST_VALIDATION');
148
+ function getValidatePodcastRight(): ActionRight {
149
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'PODCAST_VALIDATION')
150
+ ? ActionRight.Allowed
151
+ : ActionRight.DeniedNoRight;
107
152
  }
108
153
 
109
- function canEditCommentsConfigPodcast(): boolean {
110
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
154
+ function getEditCommentsConfigPodcastRight(): ActionRight {
155
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')
156
+ ? ActionRight.Allowed
157
+ : ActionRight.DeniedNoRight;
111
158
  }
112
159
 
113
- function canCreatePlaylist(): boolean {
114
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS');
160
+ // Playlist rights
161
+ function getCreatePlaylistRight(): ActionRight {
162
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS')
163
+ ? ActionRight.Allowed
164
+ : ActionRight.DeniedNoRight;
115
165
  }
116
166
 
117
- function canEditPlaylist(): boolean {
118
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS');
167
+ function getEditPlaylistRight(): ActionRight {
168
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS')
169
+ ? ActionRight.Allowed
170
+ : ActionRight.DeniedNoRight;
119
171
  }
120
172
 
121
- function canDeletePlaylist(): boolean {
122
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS');
173
+ function getDeletePlaylistRight(): ActionRight {
174
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PLAYLISTS')
175
+ ? ActionRight.Allowed
176
+ : ActionRight.DeniedNoRight;
123
177
  }
124
178
 
125
- function canCreateParticipant(): boolean {
126
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION');
179
+ // Participant rights
180
+ function getCreateParticipantRight(): ActionRight {
181
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION')
182
+ ? ActionRight.Allowed
183
+ : ActionRight.DeniedNoRight;
127
184
  }
128
185
 
129
- async function getParticipantEditRight(participantId: number|undefined): Promise<EditRight> {
186
+ async function getParticipantEditRight(participantId: number|undefined): Promise<ActionRight> {
130
187
  // New participants can be edited, and also with sufficient rights
131
- if(!participantId || roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION')) {
132
- return EditRight.Full;
133
- } else {
134
- return EditRight.None;
135
- }
188
+ return (!participantId || roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RESTRICTED_PRODUCTION'))
189
+ ? ActionRight.Allowed
190
+ : ActionRight.DeniedNoRight;
136
191
  }
137
192
 
138
193
  async function canEditParticipant(participantId: number|undefined): Promise<boolean> {
139
- const editRight = await getParticipantEditRight(participantId);
140
- return editRight === EditRight.Full;
194
+ return await getParticipantEditRight(participantId) === ActionRight.Allowed;
141
195
  }
142
196
 
143
-
144
197
  async function canDeleteParticipant(participantId: number|undefined): Promise<boolean> {
145
- const editRight = await getParticipantEditRight(participantId);
146
- return editRight === EditRight.Full;
198
+ return await getParticipantEditRight(participantId) === ActionRight.Allowed;
147
199
  }
148
200
 
149
- function canEditCodeInsertPlayer(): boolean {
150
- return roleContainsAny('ADMIN', 'ORGANISATION');
201
+ // Aggregator rights
202
+ function getCreateAggregatorRight(): ActionRight {
203
+ return roleContainsAny('ADMIN', 'ORGANISATION')
204
+ ? ActionRight.Allowed
205
+ : ActionRight.DeniedNoRight;
206
+ }
207
+
208
+ function getEditAggregatorRight(): ActionRight {
209
+ return roleContainsAny('ADMIN', 'ORGANISATION')
210
+ ? ActionRight.Allowed
211
+ : ActionRight.DeniedNoRight;
212
+ }
213
+
214
+ function getDeleteAggregatorRight(): ActionRight {
215
+ return roleContainsAny('ADMIN', 'ORGANISATION')
216
+ ? ActionRight.Allowed
217
+ : ActionRight.DeniedNoRight;
218
+ }
219
+
220
+ // Cartouchier rights
221
+ function getCreateCartouchierRight(): ActionRight {
222
+ return roleContainsAny(
223
+ 'ADMIN', 'ORGANISATION', 'PRODUCTION', 'RADIO', 'ANIMATION',
224
+ 'PODCAST_CRUD', 'RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION'
225
+ )
226
+ ? ActionRight.Allowed
227
+ : ActionRight.DeniedNoRight;
151
228
  }
152
229
 
153
- function canEditTranscript(podcast: Podcast): boolean {
154
- if(roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')) {
155
- return true;
230
+ function getEditCartouchierRight(element: Cartouchier): ActionRight {
231
+ if (roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RADIO', 'ANIMATION')) {
232
+ return ActionRight.Allowed;
156
233
  }
234
+ if (roleContainsAny('RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION', 'PODCAST_CRUD')) {
235
+ return element.ownerId !== undefined && element.ownerId !== null && element.ownerId === authStore.authProfile?.userId
236
+ ? ActionRight.Allowed
237
+ : ActionRight.DeniedNotOwner;
238
+ }
239
+ return ActionRight.DeniedNoRight;
240
+ }
157
241
 
158
- if (roleContainsAny('RESTRICTED_PRODUCTION', 'PODCAST_CRUD')) {
159
- return podcast.createdByUserId === authStore.authProfile?.userId;
242
+ function getDeleteCartouchierRight(element: Cartouchier): ActionRight {
243
+ return getEditCartouchierRight(element);
244
+ }
245
+
246
+ // PlaylistMedia rights
247
+ function getCreatePlaylistMediaRight(): ActionRight {
248
+ return roleContainsAny(
249
+ 'ADMIN', 'ORGANISATION', 'PRODUCTION', 'RADIO', 'ANIMATION',
250
+ 'PODCAST_CRUD', 'RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION'
251
+ )
252
+ ? ActionRight.Allowed
253
+ : ActionRight.DeniedNoRight;
254
+ }
255
+
256
+ function getEditPlaylistMediaRight(element: PlaylistMedia): ActionRight {
257
+ if (roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RADIO', 'ANIMATION')) {
258
+ return ActionRight.Allowed;
160
259
  }
260
+ if (roleContainsAny('RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION', 'PODCAST_CRUD')) {
261
+ return element.ownerId !== undefined && element.ownerId !== null && element.ownerId === authStore.authProfile?.userId
262
+ ? ActionRight.Allowed
263
+ : ActionRight.DeniedNotOwner;
264
+ }
265
+ return ActionRight.DeniedNoRight;
266
+ }
161
267
 
162
- return false;
268
+ function getDeletePlaylistMediaRight(element: PlaylistMedia): ActionRight {
269
+ return getEditPlaylistMediaRight(element);
163
270
  }
164
271
 
165
- function canEditTranscriptVisibility(podcast: Podcast): boolean {
166
- return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
272
+ // Media rights
273
+ function getCreateMediaRight(): ActionRight {
274
+ return roleContainsAny(
275
+ 'ADMIN', 'ORGANISATION', 'PRODUCTION', 'RADIO', 'ANIMATION',
276
+ 'PODCAST_CRUD', 'RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION'
277
+ )
278
+ ? ActionRight.Allowed
279
+ : ActionRight.DeniedNoRight;
167
280
  }
168
281
 
169
- function canEditTranslation(podcast: Podcast): boolean {
170
- return canEditTranscript(podcast);
282
+ function getEditMediaRight(element: Media): ActionRight {
283
+ if (roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION', 'RADIO', 'ANIMATION')) {
284
+ return ActionRight.Allowed;
285
+ }
286
+ if (roleContainsAny('RESTRICTED_PRODUCTION', 'RESTRICTED_ANIMATION', 'PODCAST_CRUD')) {
287
+ return element.ownerId !== undefined && element.ownerId !== null && element.ownerId === authStore.authProfile?.userId
288
+ ? ActionRight.Allowed
289
+ : ActionRight.DeniedNotOwner;
290
+ }
291
+ return ActionRight.DeniedNoRight;
171
292
  }
172
293
 
173
- function canSeeHistory(): boolean {
174
- return roleContainsAny('ADMIN', 'ORGANISATION');
294
+ function getDeleteMediaRight(element: Media): ActionRight {
295
+ return getEditMediaRight(element);
175
296
  }
176
297
 
177
- function isRestrictedProduction(): boolean {
178
- return roleContainsAny('RESTRICTED_PRODUCTION') && !roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
298
+ // Mix rights
299
+ function getCreateMixRight(): ActionRight {
300
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'RADIO')
301
+ ? ActionRight.Allowed
302
+ : ActionRight.DeniedNoRight;
179
303
  }
180
304
 
181
- /** Can the current user create an aggregator */
182
- function canCreateAggregator(): boolean {
183
- return roleContainsAny('ADMIN', 'ORGANISATION');
305
+ function getEditMixRight(_element: Mix): ActionRight {
306
+ return getCreateMixRight();
184
307
  }
185
308
 
186
- /** Can the current user edit an aggregator */
187
- function canEditAggregator(): boolean {
188
- return roleContainsAny('ADMIN', 'ORGANISATION');
309
+ function getDeleteMixRight(element: Mix): ActionRight {
310
+ return getEditMixRight(element);
189
311
  }
190
312
 
191
- /** Can the current user delete an aggregator */
192
- function canDeleteAggregator(): boolean {
313
+ // Other action rights
314
+ function getEditCodeInsertPlayerRight(): ActionRight {
315
+ return roleContainsAny('ADMIN', 'ORGANISATION')
316
+ ? ActionRight.Allowed
317
+ : ActionRight.DeniedNoRight;
318
+ }
319
+
320
+ function getEditTranscriptRight(podcast: Podcast): ActionRight {
321
+ if (roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')) {
322
+ return ActionRight.Allowed;
323
+ }
324
+ if (roleContainsAny('RESTRICTED_PRODUCTION', 'PODCAST_CRUD')) {
325
+ return podcast.createdByUserId === authStore.authProfile?.userId
326
+ ? ActionRight.Allowed
327
+ : ActionRight.DeniedNotOwner;
328
+ }
329
+ return ActionRight.DeniedNoRight;
330
+ }
331
+
332
+ function getEditTranscriptVisibilityRight(_podcast: Podcast): ActionRight {
333
+ return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')
334
+ ? ActionRight.Allowed
335
+ : ActionRight.DeniedNoRight;
336
+ }
337
+
338
+ function getEditTranslationRight(podcast: Podcast): ActionRight {
339
+ return getEditTranscriptRight(podcast);
340
+ }
341
+
342
+ // View/utility checks — outside the action pattern
343
+ function canSeeHistory(): boolean {
193
344
  return roleContainsAny('ADMIN', 'ORGANISATION');
194
345
  }
195
346
 
196
- /** Can read/edit RSS rules */
347
+ function isRestrictedProduction(): boolean {
348
+ return roleContainsAny('RESTRICTED_PRODUCTION') && !roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
349
+ }
350
+
351
+ const rightFns = {
352
+ // Emissions
353
+ getCreateEmissionRight,
354
+ getEditEmissionRight,
355
+ getDeleteEmissionRight,
356
+ getEditCommentsConfigEmissionRight,
357
+ // Podcasts
358
+ getCreatePodcastRight,
359
+ getDuplicatePodcastRight,
360
+ getEditPodcastRight,
361
+ getDeletePodcastRight,
362
+ getValidatePodcastRight,
363
+ getEditCommentsConfigPodcastRight,
364
+ // Playlists
365
+ getCreatePlaylistRight,
366
+ getEditPlaylistRight,
367
+ getDeletePlaylistRight,
368
+ // Participants (sync only)
369
+ getCreateParticipantRight,
370
+ // Aggregators
371
+ getCreateAggregatorRight,
372
+ getEditAggregatorRight,
373
+ getDeleteAggregatorRight,
374
+ // Cartouchier
375
+ getCreateCartouchierRight,
376
+ getEditCartouchierRight,
377
+ getDeleteCartouchierRight,
378
+ // PlaylistMedia
379
+ getCreatePlaylistMediaRight,
380
+ getEditPlaylistMediaRight,
381
+ getDeletePlaylistMediaRight,
382
+ // Media
383
+ getCreateMediaRight,
384
+ getEditMediaRight,
385
+ getDeleteMediaRight,
386
+ // Mix
387
+ getCreateMixRight,
388
+ getEditMixRight,
389
+ getDeleteMixRight,
390
+ // Other
391
+ getEditCodeInsertPlayerRight,
392
+ getEditTranscriptRight,
393
+ getEditTranscriptVisibilityRight,
394
+ getEditTranslationRight,
395
+ };
396
+
397
+ const canFns = deriveCanFunctions(rightFns);
398
+
197
399
  function canReadRSSRules(): boolean {
198
400
  return roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION');
199
401
  }
200
402
 
201
- /** Can edit RSS rules */
202
403
  function canEditRSSRules(): boolean {
203
404
  return canReadRSSRules();
204
405
  }
205
406
 
206
407
  return {
207
- // Emissions
208
- canCreateEmission,
209
- canEditEmission,
210
- canDeleteEmission,
211
- canEditCommentsConfigEmission,
212
-
213
- // Podcasts
214
- canCreatePodcast,
215
- canDuplicatePodcast,
216
- canEditPodcast,
217
- canDeletePodcast,
218
- canValidatePodcast,
219
- canEditCommentsConfigPodcast,
220
-
221
- // Playlists
222
- canCreatePlaylist,
223
- canEditPlaylist,
224
- canDeletePlaylist,
225
-
226
- // Participants
227
- canCreateParticipant,
408
+ ...rightFns,
409
+ ...canFns,
410
+ // Async participant (kept manually — async functions excluded from deriveCanFunctions)
228
411
  getParticipantEditRight,
229
412
  canEditParticipant,
230
413
  canDeleteParticipant,
231
-
232
- // Aggregators
233
- canCreateAggregator,
234
- canEditAggregator,
235
- canDeleteAggregator,
236
-
237
- // RSS Rules
414
+ // RSS Rules (manual — outside the getRightXxx convention)
238
415
  canReadRSSRules,
239
416
  canEditRSSRules,
240
-
241
- // Other
242
- canEditCodeInsertPlayer,
243
- canEditTranscript,
244
- canEditTranslation,
245
- canEditTranscriptVisibility,
417
+ // View/utility checks
246
418
  canSeeHistory,
247
- isRestrictedProduction
248
- }
249
- }
419
+ isRestrictedProduction,
420
+ };
421
+ };