@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.
- package/CHANGELOG.md +27 -0
- package/index.ts +9 -10
- package/package.json +7 -6
- package/src/api/index.ts +9 -0
- package/src/api/mediathequeApi.ts +75 -0
- package/src/components/composable/useRights.ts +316 -144
- package/src/components/display/RightsIndicator.vue +215 -0
- package/src/components/misc/ClassicAlert.vue +15 -10
- package/src/components/misc/ClassicNav.vue +5 -0
- package/src/components/misc/ClassicPopover.vue +34 -65
- package/src/components/misc/ClassicTabs.vue +57 -0
- package/src/locale/de.json +6 -1
- package/src/locale/en.json +6 -1
- package/src/locale/es.json +6 -1
- package/src/locale/fr.json +7 -1
- package/src/locale/it.json +6 -1
- package/src/locale/sl.json +6 -1
- package/src/stores/FilterStore.ts +0 -23
- package/src/stores/class/cartouchier/cartouchier.ts +2 -0
- package/src/stores/class/general/media.ts +2 -0
- package/src/stores/class/radio/mix.ts +2 -0
- package/src/stores/class/radio/playlistMedia.ts +2 -0
- package/tests/components/composable/useRights.spec.ts +328 -1
- package/tests/components/display/RightsIndicator.spec.ts +345 -0
- package/tests/components/misc/ClassicPopover.spec.ts +64 -51
|
@@ -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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
//
|
|
29
|
-
function
|
|
30
|
-
|
|
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
|
|
81
|
+
function getEditEmissionRight(emission: Emission): ActionRight {
|
|
34
82
|
if (
|
|
35
|
-
|
|
36
|
-
(
|
|
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
|
|
86
|
+
return ActionRight.Allowed;
|
|
41
87
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
110
|
+
// Podcast rights
|
|
111
|
+
function getCreatePodcastRight(): ActionRight {
|
|
59
112
|
return roleContainsAny(
|
|
60
|
-
'ADMIN',
|
|
61
|
-
'
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
81
|
-
// Full rights users can edit any podcast
|
|
127
|
+
function getEditPodcastRight(podcast: Podcast): ActionRight {
|
|
82
128
|
if (roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')) {
|
|
83
|
-
return
|
|
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
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
return canEditPodcast(podcast);
|
|
144
|
+
function getDeletePodcastRight(podcast: Podcast): ActionRight {
|
|
145
|
+
return getEditPodcastRight(podcast);
|
|
103
146
|
}
|
|
104
147
|
|
|
105
|
-
function
|
|
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
|
|
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
|
-
|
|
114
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
126
|
-
|
|
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<
|
|
186
|
+
async function getParticipantEditRight(participantId: number|undefined): Promise<ActionRight> {
|
|
130
187
|
// New participants can be edited, and also with sufficient rights
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
return editRight === EditRight.Full;
|
|
198
|
+
return await getParticipantEditRight(participantId) === ActionRight.Allowed;
|
|
147
199
|
}
|
|
148
200
|
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
154
|
-
if(roleContainsAny('ADMIN', 'ORGANISATION', 'PRODUCTION')) {
|
|
155
|
-
return
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
268
|
+
function getDeletePlaylistMediaRight(element: PlaylistMedia): ActionRight {
|
|
269
|
+
return getEditPlaylistMediaRight(element);
|
|
163
270
|
}
|
|
164
271
|
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
170
|
-
|
|
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
|
|
174
|
-
return
|
|
294
|
+
function getDeleteMediaRight(element: Media): ActionRight {
|
|
295
|
+
return getEditMediaRight(element);
|
|
175
296
|
}
|
|
176
297
|
|
|
177
|
-
|
|
178
|
-
|
|
298
|
+
// Mix rights
|
|
299
|
+
function getCreateMixRight(): ActionRight {
|
|
300
|
+
return roleContainsAny('ADMIN', 'ORGANISATION', 'RADIO')
|
|
301
|
+
? ActionRight.Allowed
|
|
302
|
+
: ActionRight.DeniedNoRight;
|
|
179
303
|
}
|
|
180
304
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return roleContainsAny('ADMIN', 'ORGANISATION');
|
|
305
|
+
function getEditMixRight(_element: Mix): ActionRight {
|
|
306
|
+
return getCreateMixRight();
|
|
184
307
|
}
|
|
185
308
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return roleContainsAny('ADMIN', 'ORGANISATION');
|
|
309
|
+
function getDeleteMixRight(element: Mix): ActionRight {
|
|
310
|
+
return getEditMixRight(element);
|
|
189
311
|
}
|
|
190
312
|
|
|
191
|
-
|
|
192
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
};
|