@iblai/data-layer 1.34.0 → 1.36.0
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/dist/index.d.ts +211 -53
- package/dist/index.esm.js +355 -105
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +355 -105
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/config.d.ts +8 -0
- package/dist/src/constants.d.ts +17 -1
- package/dist/src/features/chat-files/__tests__/api-slice.test.d.ts +1 -0
- package/dist/src/features/course-metadata/__tests__/api-slice.test.d.ts +1 -0
- package/dist/src/features/course-metadata/api-slice.d.ts +162 -108
- package/dist/src/features/retirement/__tests__/api-slice.test.d.ts +1 -0
- package/dist/src/features/search/types.d.ts +2 -0
- package/dist/src/features/studio-authoring/__tests__/course-key.test.d.ts +1 -0
- package/dist/src/features/studio-authoring/api-slice.d.ts +12 -4
- package/dist/src/features/studio-authoring/constants.d.ts +4 -0
- package/dist/src/features/studio-authoring/course-key.d.ts +16 -0
- package/dist/src/features/studio-authoring/types.d.ts +12 -2
- package/dist/src/features/tenant/__tests__/api-slice.test.d.ts +1 -0
- package/dist/src/features/tenant/api-slice.d.ts +66 -21
- package/dist/src/features/tenant/constants.d.ts +1 -1
- package/dist/src/features/user/__tests__/roles-mapping.test.d.ts +1 -0
- package/dist/src/features/user/__tests__/user-metadata.test.d.ts +1 -0
- package/dist/src/features/user/api-slice.d.ts +15 -13
- package/dist/src/features/user/constants.d.ts +11 -1
- package/dist/src/features/user/roles-mapping.d.ts +9 -0
- package/dist/src/features/user/types.d.ts +26 -11
- package/dist/src/features/utils.d.ts +12 -0
- package/dist/src/utils/index.d.ts +4 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39049,10 +39049,26 @@ const TENANT_IDENTIFIERS = {
|
|
|
39049
39049
|
exports.SERVICES = void 0;
|
|
39050
39050
|
(function (SERVICES) {
|
|
39051
39051
|
SERVICES["LEGACY_LMS"] = "LEGACY_LMS";
|
|
39052
|
+
/**
|
|
39053
|
+
* The Open edX LMS. With `Config.edxProxyEnabled` the call goes through DM's
|
|
39054
|
+
* `/v1/lms/{edx_path}` proxy with the DM token (DM mints the edX JWT
|
|
39055
|
+
* server-side); otherwise it goes to the LMS host with the edX JWT. The
|
|
39056
|
+
* proxy admits stock edX paths only: the `/api/ibl/...` family is refused,
|
|
39057
|
+
* and any course or org a path names must belong to the token's platform.
|
|
39058
|
+
*/
|
|
39052
39059
|
SERVICES["LMS"] = "LMS";
|
|
39053
39060
|
SERVICES["DM"] = "DM";
|
|
39054
39061
|
SERVICES["AXD"] = "AXD";
|
|
39062
|
+
/** Open edX Studio (CMS); proxied through `/v1/studio/{edx_path}` like `LMS`. */
|
|
39055
39063
|
SERVICES["STUDIO"] = "STUDIO";
|
|
39064
|
+
/**
|
|
39065
|
+
* The LMS host with the edX JWT, whatever `Config.edxProxyEnabled` says. Only
|
|
39066
|
+
* for calls the proxy cannot serve: the login/token bootstrap and the
|
|
39067
|
+
* `/api/ibl/...` paths DM has no replacement for yet.
|
|
39068
|
+
*/
|
|
39069
|
+
SERVICES["LMS_DIRECT"] = "LMS_DIRECT";
|
|
39070
|
+
/** The Studio host with its session cookie, CSRF token and edX JWT; see `LMS_DIRECT`. */
|
|
39071
|
+
SERVICES["STUDIO_DIRECT"] = "STUDIO_DIRECT";
|
|
39056
39072
|
})(exports.SERVICES || (exports.SERVICES = {}));
|
|
39057
39073
|
/**
|
|
39058
39074
|
* Client errors a retry cannot fix.
|
|
@@ -39157,6 +39173,14 @@ Config.axdUrl = 'https://base.manager.iblai.app';
|
|
|
39157
39173
|
Config.mentorIframeUrl = 'https://mentor.iblai.tech';
|
|
39158
39174
|
Config.studioUrl = 'https://studio.iblai.app';
|
|
39159
39175
|
Config.httpErrorHandlers = {};
|
|
39176
|
+
/**
|
|
39177
|
+
* Route `SERVICES.LMS` / `SERVICES.STUDIO` through DM's edX proxy
|
|
39178
|
+
* (`{dmUrl}/v1/lms`, `{dmUrl}/v1/studio`) with the DM token instead of
|
|
39179
|
+
* calling the edX hosts with the edX JWT. Needs DM 4.444.0+ with its proxy
|
|
39180
|
+
* rules synced, so it is off by default and turned on per deployment via
|
|
39181
|
+
* `initializeDataLayer(..., { edxProxy: true })`.
|
|
39182
|
+
*/
|
|
39183
|
+
Config.edxProxyEnabled = false;
|
|
39160
39184
|
/**
|
|
39161
39185
|
* Optional override for how expired auth tokens are re-minted after a 401.
|
|
39162
39186
|
* Resolve `true` once fresh tokens are in storage. Left unset, the data
|
|
@@ -39217,17 +39241,19 @@ const iblFakeBaseQuery = react.retry(fakeBaseQueryWithLogging, {
|
|
|
39217
39241
|
},
|
|
39218
39242
|
});
|
|
39219
39243
|
|
|
39244
|
+
// The login / token bootstrap runs before the SPA holds a DM token (and mints
|
|
39245
|
+
// it), so these stay on the LMS host whatever `Config.edxProxyEnabled` says.
|
|
39220
39246
|
const AUTH_ENDPOINTS = {
|
|
39221
39247
|
GET_MFE_CONTEXT: {
|
|
39222
|
-
service: exports.SERVICES.
|
|
39248
|
+
service: exports.SERVICES.LMS_DIRECT,
|
|
39223
39249
|
path: () => `/api/mfe_context`,
|
|
39224
39250
|
},
|
|
39225
39251
|
APPLE_EXCHANGE_TOKEN: {
|
|
39226
|
-
service: exports.SERVICES.
|
|
39252
|
+
service: exports.SERVICES.LMS_DIRECT,
|
|
39227
39253
|
path: () => `/oauth2/exchange_access_token/apple-id/`,
|
|
39228
39254
|
},
|
|
39229
39255
|
GET_APP_TOKENS: {
|
|
39230
|
-
service: exports.SERVICES.
|
|
39256
|
+
service: exports.SERVICES.LMS_DIRECT,
|
|
39231
39257
|
path: () => '/api/ibl/manager/consolidated-token/proxy/',
|
|
39232
39258
|
},
|
|
39233
39259
|
REFRESH_JWT_TOKEN: {
|
|
@@ -39344,6 +39370,15 @@ const refreshAuthTokenOnce = async () => {
|
|
|
39344
39370
|
return refreshInFlight;
|
|
39345
39371
|
};
|
|
39346
39372
|
|
|
39373
|
+
/**
|
|
39374
|
+
* True when `service` is `LMS` or `STUDIO` and `Config.edxProxyEnabled` sends
|
|
39375
|
+
* it through DM's edX proxy with the DM token.
|
|
39376
|
+
*/
|
|
39377
|
+
const isEdxProxied = (service) => Config.edxProxyEnabled && (service === exports.SERVICES.LMS || service === exports.SERVICES.STUDIO);
|
|
39378
|
+
/** The LMS host on its own origin, authenticated with the edX JWT. */
|
|
39379
|
+
const isDirectLms = (service) => service === exports.SERVICES.LMS_DIRECT || (service === exports.SERVICES.LMS && !Config.edxProxyEnabled);
|
|
39380
|
+
/** The Studio host on its own origin: session cookie + CSRF token + edX JWT. */
|
|
39381
|
+
const isDirectStudio = (service) => service === exports.SERVICES.STUDIO_DIRECT || (service === exports.SERVICES.STUDIO && !Config.edxProxyEnabled);
|
|
39347
39382
|
/**
|
|
39348
39383
|
* Get the service URL based on the provided service type.
|
|
39349
39384
|
*/
|
|
@@ -39352,15 +39387,44 @@ const getServiceUrl = (service) => {
|
|
|
39352
39387
|
case exports.SERVICES.LEGACY_LMS:
|
|
39353
39388
|
return Config.legacyLmsUrl;
|
|
39354
39389
|
case exports.SERVICES.LMS:
|
|
39390
|
+
return Config.edxProxyEnabled ? `${Config.dmUrl}/v1/lms` : Config.lmsUrl;
|
|
39391
|
+
case exports.SERVICES.LMS_DIRECT:
|
|
39355
39392
|
return Config.lmsUrl;
|
|
39356
39393
|
case exports.SERVICES.DM:
|
|
39357
39394
|
return Config.dmUrl;
|
|
39358
39395
|
case exports.SERVICES.STUDIO:
|
|
39396
|
+
return Config.edxProxyEnabled ? `${Config.dmUrl}/v1/studio` : Config.studioUrl;
|
|
39397
|
+
case exports.SERVICES.STUDIO_DIRECT:
|
|
39359
39398
|
return Config.studioUrl;
|
|
39360
39399
|
default:
|
|
39361
39400
|
return Config.dmUrl;
|
|
39362
39401
|
}
|
|
39363
39402
|
};
|
|
39403
|
+
/**
|
|
39404
|
+
* The signed-in user's username from storage (`userData.user_nicename`), or
|
|
39405
|
+
* null. DM's user endpoints take `username=<self>` explicitly where the edX
|
|
39406
|
+
* ones inferred the caller from the JWT, so the endpoints that moved to DM
|
|
39407
|
+
* read it from here when the caller does not pass one.
|
|
39408
|
+
*/
|
|
39409
|
+
const getStoredUsername = async () => {
|
|
39410
|
+
var _a;
|
|
39411
|
+
const storage = IblDataLayer.storage;
|
|
39412
|
+
if (!storage) {
|
|
39413
|
+
return null;
|
|
39414
|
+
}
|
|
39415
|
+
const raw = await storage.getItem(STORAGE_KEYS.USER_DATA);
|
|
39416
|
+
if (!raw) {
|
|
39417
|
+
return null;
|
|
39418
|
+
}
|
|
39419
|
+
try {
|
|
39420
|
+
const data = (typeof raw === 'string' ? JSON.parse(raw) : raw);
|
|
39421
|
+
const username = (_a = data === null || data === void 0 ? void 0 : data.user_nicename) !== null && _a !== void 0 ? _a : data === null || data === void 0 ? void 0 : data.username;
|
|
39422
|
+
return typeof username === 'string' && username ? username : null;
|
|
39423
|
+
}
|
|
39424
|
+
catch (_b) {
|
|
39425
|
+
return null;
|
|
39426
|
+
}
|
|
39427
|
+
};
|
|
39364
39428
|
/**
|
|
39365
39429
|
* Read a cookie value by name. Returns null outside the browser or when the
|
|
39366
39430
|
* cookie is absent. Used for Studio's CSRF token (`csrftoken`).
|
|
@@ -39381,22 +39445,26 @@ const getHeaders = async (service) => {
|
|
|
39381
39445
|
throw new Error('Storage not initialized. Please ensure the data layer is properly initialized before making API calls.');
|
|
39382
39446
|
}
|
|
39383
39447
|
let token = null;
|
|
39384
|
-
|
|
39385
|
-
|
|
39386
|
-
|
|
39387
|
-
|
|
39388
|
-
|
|
39389
|
-
|
|
39390
|
-
|
|
39391
|
-
|
|
39392
|
-
token = await IblDataLayer.storage.getItem(STORAGE_KEYS.EDX_TOKEN_KEY);
|
|
39448
|
+
if (service === exports.SERVICES.DM || isEdxProxied(service)) {
|
|
39449
|
+
token = await IblDataLayer.storage.getItem(STORAGE_KEYS.DM_TOKEN_KEY);
|
|
39450
|
+
}
|
|
39451
|
+
else if (service === exports.SERVICES.AXD) {
|
|
39452
|
+
token = await IblDataLayer.storage.getItem(STORAGE_KEYS.AXD_TOKEN_KEY);
|
|
39453
|
+
}
|
|
39454
|
+
else {
|
|
39455
|
+
token = await IblDataLayer.storage.getItem(STORAGE_KEYS.EDX_TOKEN_KEY);
|
|
39393
39456
|
}
|
|
39394
39457
|
if (!token || token.trim() === '') {
|
|
39395
39458
|
return; // No Authorization header when no valid token
|
|
39396
39459
|
}
|
|
39460
|
+
// A storage that JSON-encodes strings on write hands the token back as
|
|
39461
|
+
// `"token"`; send it bare, or DM refuses it as invalid.
|
|
39462
|
+
if (token.startsWith('"') && token.endsWith('"')) {
|
|
39463
|
+
token = token.slice(1, -1);
|
|
39464
|
+
}
|
|
39397
39465
|
let prefix;
|
|
39398
39466
|
// Studio (Open edX CMS) accepts the same edX token as the LMS.
|
|
39399
|
-
if (service
|
|
39467
|
+
if (isDirectLms(service) || isDirectStudio(service)) {
|
|
39400
39468
|
if (token.length > 30) {
|
|
39401
39469
|
prefix = 'JWT';
|
|
39402
39470
|
}
|
|
@@ -39407,7 +39475,7 @@ const getHeaders = async (service) => {
|
|
|
39407
39475
|
else {
|
|
39408
39476
|
prefix = 'Token';
|
|
39409
39477
|
}
|
|
39410
|
-
return
|
|
39478
|
+
return { Authorization: `${prefix} ${token}` };
|
|
39411
39479
|
};
|
|
39412
39480
|
/**
|
|
39413
39481
|
* Status codes that should not be retried for queryFn endpoints.
|
|
@@ -39542,7 +39610,7 @@ const isErrorObject = (data) => {
|
|
|
39542
39610
|
const baseQuery = (service, jsonContentType = true, contentType, skipAuth = false) => query.fetchBaseQuery({
|
|
39543
39611
|
baseUrl: getServiceUrl(service),
|
|
39544
39612
|
timeout: 30000, // 30 second timeout
|
|
39545
|
-
credentials: service === exports.SERVICES.LEGACY_LMS || service
|
|
39613
|
+
credentials: service === exports.SERVICES.LEGACY_LMS || isDirectStudio(service) ? 'include' : 'omit',
|
|
39546
39614
|
prepareHeaders: async (headers) => {
|
|
39547
39615
|
// Only add auth headers if skipAuth is false
|
|
39548
39616
|
if (!skipAuth) {
|
|
@@ -39561,8 +39629,9 @@ const baseQuery = (service, jsonContentType = true, contentType, skipAuth = fals
|
|
|
39561
39629
|
headers.set('Content-Type', contentType);
|
|
39562
39630
|
}
|
|
39563
39631
|
// Studio (Open edX CMS) endpoints require the Django CSRF token and only
|
|
39564
|
-
// return JSON when asked for it explicitly.
|
|
39565
|
-
|
|
39632
|
+
// return JSON when asked for it explicitly. Through DM's proxy neither
|
|
39633
|
+
// applies: no cookies travel and DM mints the edX JWT.
|
|
39634
|
+
if (isDirectStudio(service)) {
|
|
39566
39635
|
const csrfToken = getCookieValue('csrftoken');
|
|
39567
39636
|
if (csrfToken) {
|
|
39568
39637
|
headers.set('X-CSRFToken', csrfToken);
|
|
@@ -39573,7 +39642,7 @@ const baseQuery = (service, jsonContentType = true, contentType, skipAuth = fals
|
|
|
39573
39642
|
headers.set('X-Requested-With', 'XMLHttpRequest');
|
|
39574
39643
|
}
|
|
39575
39644
|
// Add CSRF protection headers for LMS service
|
|
39576
|
-
if (service
|
|
39645
|
+
if (isDirectLms(service)) {
|
|
39577
39646
|
// Use mentorIframeUrl for both origin and referer
|
|
39578
39647
|
// This matches the pattern used in loginCompleteApi.ts
|
|
39579
39648
|
headers.set('Referer', Config.mentorIframeUrl);
|
|
@@ -40098,6 +40167,9 @@ const chatFilesApiSlice = react.createApi({
|
|
|
40098
40167
|
method: 'POST',
|
|
40099
40168
|
body: requestBody,
|
|
40100
40169
|
service: exports.SERVICES.DM,
|
|
40170
|
+
// A thrown error reaches the caller as `{}`; the chat needs the 400
|
|
40171
|
+
// body to tell an agent's file-type refusal from other failures.
|
|
40172
|
+
returnErrorStatuses: [400],
|
|
40101
40173
|
}),
|
|
40102
40174
|
}),
|
|
40103
40175
|
}),
|
|
@@ -40548,13 +40620,15 @@ const authApiSlice = react.createApi({
|
|
|
40548
40620
|
const { useGetMfeContextQuery, useExchangeAppleTokenMutation, useGetAppTokensMutation, useLazyRefreshJwtTokenQuery, } = authApiSlice;
|
|
40549
40621
|
|
|
40550
40622
|
const TENANTS_ENDPOINTS = {
|
|
40623
|
+
// The DM view the edX `/api/ibl/users/manage/platform/` endpoint used to
|
|
40624
|
+
// proxy; takes `username=<self>`.
|
|
40551
40625
|
GET_USER_TENANTS: {
|
|
40552
|
-
service: exports.SERVICES.
|
|
40553
|
-
path: () => `/api/
|
|
40626
|
+
service: exports.SERVICES.DM,
|
|
40627
|
+
path: () => `/api/core/users/platforms/`,
|
|
40554
40628
|
},
|
|
40555
40629
|
};
|
|
40556
40630
|
const TENANTS_QUERY_KEYS = {
|
|
40557
|
-
GET_USER_TENANTS: () => [
|
|
40631
|
+
GET_USER_TENANTS: () => ['USER_TENANTS'],
|
|
40558
40632
|
};
|
|
40559
40633
|
|
|
40560
40634
|
const tenantApiSlice = react.createApi({
|
|
@@ -40570,11 +40644,18 @@ const tenantApiSlice = react.createApi({
|
|
|
40570
40644
|
...buildEndpointFromDmServiceLegacy(CoreService.coreOrgsMetadataUpdate),
|
|
40571
40645
|
invalidatesTags: ['TenantMetadata'],
|
|
40572
40646
|
}),
|
|
40647
|
+
/** The platforms the signed-in user is linked to. `username` defaults to the stored user. */
|
|
40573
40648
|
getUserTenants: builder.query({
|
|
40574
|
-
queryFn: async () => {
|
|
40649
|
+
queryFn: async (args) => {
|
|
40650
|
+
var _a;
|
|
40575
40651
|
try {
|
|
40576
|
-
const
|
|
40577
|
-
|
|
40652
|
+
const username = (_a = args === null || args === void 0 ? void 0 : args.username) !== null && _a !== void 0 ? _a : (await getStoredUsername());
|
|
40653
|
+
if (!username) {
|
|
40654
|
+
return { error: { status: 'CUSTOM_ERROR', data: 'No username available' } };
|
|
40655
|
+
}
|
|
40656
|
+
const { service, path } = TENANTS_ENDPOINTS.GET_USER_TENANTS;
|
|
40657
|
+
const authHeaders = await getHeaders(service);
|
|
40658
|
+
const response = await fetch(`${getServiceUrl(service)}${path()}?username=${encodeURIComponent(username)}`, {
|
|
40578
40659
|
headers: {
|
|
40579
40660
|
...authHeaders,
|
|
40580
40661
|
},
|
|
@@ -41058,28 +41139,41 @@ const platformApiReducer = platformApiSlice.reducer;
|
|
|
41058
41139
|
const { useUsersGradesPassedQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, usePlatformUserGroupsQuery, usePlatformInvitationsQuery, usePlatformUsersQuery, useInviteUserMutation, useUpdatePlatformUserRoleWithPoliciesMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetPlatformUserGroupDetailsQuery, useGetApplicationStatusQuery, useLazyGetApplicationStatusQuery, useGetApplicationFormQuery, useLazyGetApplicationFormQuery, useCreateApplicationDraftMutation, useUpdateApplicationMutation, useSubmitApplicationMutation, useUploadApplicationAttachmentMutation, useDeleteApplicationAttachmentMutation, useGetApplicationFormsQuery, useLazyGetApplicationFormsQuery, useGetApplicationFormDetailQuery, useLazyGetApplicationFormDetailQuery, useCreateApplicationFormMutation, useUpdateApplicationFormMutation, useGetApplicationsPipelineQuery, useLazyGetApplicationsPipelineQuery, useGetApplicationsPipelineStatsQuery, useListOwnApplicationsQuery, useLazyListOwnApplicationsQuery, useRenewApplicationMutation, useWithdrawApplicationMutation, useGetApplicationAdminDetailQuery, useLazyGetApplicationAdminDetailQuery, useTransitionApplicationMutation, useCreateApplicationWaiverMutation, useDeleteApplicationWaiverMutation, useSubmitApplicationOverrideMutation, useGetApplicationNotesQuery, useCreateApplicationNoteMutation, useUpdateApplicationNoteMutation, useDeleteApplicationNoteMutation, useLazyGetApplicationAttachmentFileQuery, useGetApplicationBlocksQuery, useDeleteApplicationBlockMutation, useGetApplicationPlacementsQuery, useCreateApplicationPlacementsMutation, useUpdateApplicationPlacementMutation, useGetApplicationCourseAssignmentsQuery, useCreateApplicationCourseAssignmentsMutation, useDeleteApplicationCourseAssignmentMutation, useInviteApplicationCandidateMutation, useLinkApplicationCandidateMutation, useUserPlatformsQuery, useLazyUserPlatformsQuery, } = platformApiSlice;
|
|
41059
41140
|
|
|
41060
41141
|
const USERS_ENDPOINTS = {
|
|
41142
|
+
// The merged edX + DM profile, served by DM itself (the same payload the edX
|
|
41143
|
+
// `/api/ibl/users/manage/metadata/` endpoint used to proxy).
|
|
41061
41144
|
GET_USER_METADATA: {
|
|
41062
|
-
service: exports.SERVICES.
|
|
41145
|
+
service: exports.SERVICES.DM,
|
|
41146
|
+
path: () => `/api/core/users/metadata/proxy/`,
|
|
41147
|
+
},
|
|
41148
|
+
// TODO(iblai-platform#2707 B1): the write still goes to the edX plugin. Its
|
|
41149
|
+
// edX half could be a merge-patch on `/api/user/v1/accounts/<self>`, but the
|
|
41150
|
+
// DM-only fields (title, about, public_metadata, tour/onboarding flags) have
|
|
41151
|
+
// no self-write endpoint on DM yet, and splitting the call would drop them.
|
|
41152
|
+
UPDATE_USER_METADATA: {
|
|
41153
|
+
service: exports.SERVICES.LMS_DIRECT,
|
|
41063
41154
|
path: () => `/api/ibl/users/manage/metadata/`,
|
|
41064
41155
|
},
|
|
41065
41156
|
RESET_PASSWORD: {
|
|
41066
41157
|
service: exports.SERVICES.LEGACY_LMS,
|
|
41067
41158
|
path: () => `/ibl/edx/account/api/reset-password-token`,
|
|
41068
41159
|
},
|
|
41160
|
+
// Platform-admin flag on a user's platform link; DM syncs the matching edX
|
|
41161
|
+
// org role itself.
|
|
41069
41162
|
UPDATE_USER_ROLES: {
|
|
41070
|
-
service: exports.SERVICES.
|
|
41163
|
+
service: exports.SERVICES.DM,
|
|
41164
|
+
path: () => `/api/core/users/platforms/`,
|
|
41165
|
+
},
|
|
41166
|
+
/** @deprecated The edX role write behind the legacy `{ username, org, role, active }` shape. */
|
|
41167
|
+
UPDATE_USER_ROLES_EDX: {
|
|
41168
|
+
service: exports.SERVICES.LMS_DIRECT,
|
|
41071
41169
|
path: () => `/api/ibl/users/manage/roles/`,
|
|
41072
41170
|
},
|
|
41073
|
-
//
|
|
41074
|
-
//
|
|
41075
|
-
//
|
|
41076
|
-
// resolvable there; `lmsService` is the fallback for deployments without a
|
|
41077
|
-
// reachable Studio host. Both hosts share one cache tag, so this stays a
|
|
41078
|
-
// single endpoint rather than two.
|
|
41171
|
+
// The caller's course access roles from the edX enrollment API, mapped onto
|
|
41172
|
+
// the keys the plugin listing used (`course-staff`, ...). Org-wide roles live
|
|
41173
|
+
// on the DM side (`/api/core/users/platforms/` → is_admin / is_staff).
|
|
41079
41174
|
GET_USER_ROLES: {
|
|
41080
|
-
service: exports.SERVICES.
|
|
41081
|
-
|
|
41082
|
-
path: () => `/api/ibl/users/manage/roles/`,
|
|
41175
|
+
service: exports.SERVICES.LMS,
|
|
41176
|
+
path: () => `/api/enrollment/v1/roles/`,
|
|
41083
41177
|
},
|
|
41084
41178
|
UPLOAD_PROFILE_IMAGE: {
|
|
41085
41179
|
service: exports.SERVICES.LMS,
|
|
@@ -41113,11 +41207,13 @@ const USERS_ENDPOINTS = {
|
|
|
41113
41207
|
};
|
|
41114
41208
|
const USERS_QUERY_KEYS = {
|
|
41115
41209
|
GET_USER_METADATA: () => ['USER_METADATA'],
|
|
41210
|
+
UPDATE_USER_METADATA: () => ['USER_METADATA'],
|
|
41116
41211
|
GET_USER_PLATFORM_METADATA: () => ['USER_PLATFORM_METADATA'],
|
|
41117
41212
|
GET_USER_METADATA_EDX: () => ['USER_METADATA_EDX'],
|
|
41118
41213
|
GET_USER_PROFILE_SUMMARY: () => ['USER_PROFILE_SUMMARY'],
|
|
41119
41214
|
RESET_PASSWORD: () => ['RESET_PASSWORD'],
|
|
41120
41215
|
UPDATE_USER_ROLES: () => ['UPDATE_USER_ROLES'],
|
|
41216
|
+
UPDATE_USER_ROLES_EDX: () => ['UPDATE_USER_ROLES'],
|
|
41121
41217
|
GET_USER_ROLES: () => ['USER_ROLES'],
|
|
41122
41218
|
UPLOAD_PROFILE_IMAGE: () => ['UPLOAD_PROFILE_IMAGE'],
|
|
41123
41219
|
EDX_UPDATE_USER_METADATA: () => ['EDX_UPDATE_USER_METADATA'],
|
|
@@ -41126,6 +41222,44 @@ const USERS_QUERY_KEYS = {
|
|
|
41126
41222
|
};
|
|
41127
41223
|
const USERS_REDUCER_PATH = 'userApiSlice';
|
|
41128
41224
|
|
|
41225
|
+
/**
|
|
41226
|
+
* Open edX `CourseAccessRole` names → the keys the edX plugin's role listing
|
|
41227
|
+
* used, which the role filters and the apps' staff checks are written against.
|
|
41228
|
+
*/
|
|
41229
|
+
const EDX_ROLE_KEYS = {
|
|
41230
|
+
staff: 'staff',
|
|
41231
|
+
instructor: 'instructor',
|
|
41232
|
+
limited_staff: 'limited-staff',
|
|
41233
|
+
finance_admin: 'finance-admin',
|
|
41234
|
+
sales_admin: 'sales-admin',
|
|
41235
|
+
beta_testers: 'beta-tester',
|
|
41236
|
+
library_user: 'library-user',
|
|
41237
|
+
ccx_coach: 'ccx-coach',
|
|
41238
|
+
data_researcher: 'data-researcher',
|
|
41239
|
+
course_creator_group: 'course-creator',
|
|
41240
|
+
support: 'support-staff',
|
|
41241
|
+
};
|
|
41242
|
+
const roleKey = (edxRole) => { var _a; return (_a = EDX_ROLE_KEYS[edxRole]) !== null && _a !== void 0 ? _a : edxRole.replace(/_/g, '-'); };
|
|
41243
|
+
/**
|
|
41244
|
+
* Maps the edX enrollment roles API (`GET /api/enrollment/v1/roles/`) onto the
|
|
41245
|
+
* `UserRole[]` shape the plugin listing returned: course rows become
|
|
41246
|
+
* `course-<role>`, org rows `org-<role>`, and the global `is_staff` flag a
|
|
41247
|
+
* `staff` row. The edX API only lists `CourseAccessRole` rows, so org-wide
|
|
41248
|
+
* roles granted on the DM side (platform admin / staff) are not included.
|
|
41249
|
+
*/
|
|
41250
|
+
const mapEdxUserRoles = (response) => {
|
|
41251
|
+
var _a;
|
|
41252
|
+
const roles = ((_a = response === null || response === void 0 ? void 0 : response.roles) !== null && _a !== void 0 ? _a : []).map(({ role, org, course_id }) => {
|
|
41253
|
+
const key = roleKey(role);
|
|
41254
|
+
const scoped = course_id ? `course-${key}` : org ? `org-${key}` : key;
|
|
41255
|
+
return { role: scoped, org: org !== null && org !== void 0 ? org : '', course: course_id !== null && course_id !== void 0 ? course_id : '' };
|
|
41256
|
+
});
|
|
41257
|
+
if (response === null || response === void 0 ? void 0 : response.is_staff) {
|
|
41258
|
+
roles.unshift({ role: 'staff', org: '', course: '' });
|
|
41259
|
+
}
|
|
41260
|
+
return roles;
|
|
41261
|
+
};
|
|
41262
|
+
|
|
41129
41263
|
const userApiSlice = react.createApi({
|
|
41130
41264
|
reducerPath: USERS_REDUCER_PATH,
|
|
41131
41265
|
baseQuery: iblFetchBaseQuery,
|
|
@@ -41148,8 +41282,8 @@ const userApiSlice = react.createApi({
|
|
|
41148
41282
|
}),
|
|
41149
41283
|
updateUserMetadata: builder.mutation({
|
|
41150
41284
|
query: (args) => ({
|
|
41151
|
-
url: USERS_ENDPOINTS.
|
|
41152
|
-
service: USERS_ENDPOINTS.
|
|
41285
|
+
url: USERS_ENDPOINTS.UPDATE_USER_METADATA.path(),
|
|
41286
|
+
service: USERS_ENDPOINTS.UPDATE_USER_METADATA.service,
|
|
41153
41287
|
body: args,
|
|
41154
41288
|
method: 'POST',
|
|
41155
41289
|
params: { username: args.username },
|
|
@@ -41158,8 +41292,8 @@ const userApiSlice = react.createApi({
|
|
|
41158
41292
|
}),
|
|
41159
41293
|
updateUserAccount: builder.mutation({
|
|
41160
41294
|
query: (args) => ({
|
|
41161
|
-
url: USERS_ENDPOINTS.
|
|
41162
|
-
service: USERS_ENDPOINTS.
|
|
41295
|
+
url: USERS_ENDPOINTS.UPDATE_USER_METADATA.path(),
|
|
41296
|
+
service: USERS_ENDPOINTS.UPDATE_USER_METADATA.service,
|
|
41163
41297
|
body: args,
|
|
41164
41298
|
method: 'POST',
|
|
41165
41299
|
params: { username: args.username },
|
|
@@ -41188,45 +41322,45 @@ const userApiSlice = react.createApi({
|
|
|
41188
41322
|
};
|
|
41189
41323
|
},
|
|
41190
41324
|
}),
|
|
41325
|
+
/**
|
|
41326
|
+
* Make a user a platform admin, or demote them. Takes the DM platform-link
|
|
41327
|
+
* shape (`user_id`, `platform_key`, `is_admin`); DM syncs the edX org role.
|
|
41328
|
+
* The legacy `{ username, org, role, active }` shape still writes the edX
|
|
41329
|
+
* role directly and is deprecated.
|
|
41330
|
+
*/
|
|
41191
41331
|
updateUserRole: builder.mutation({
|
|
41192
|
-
query: (args) =>
|
|
41193
|
-
|
|
41194
|
-
|
|
41195
|
-
|
|
41196
|
-
|
|
41197
|
-
|
|
41332
|
+
query: (args) => 'user_id' in args
|
|
41333
|
+
? {
|
|
41334
|
+
url: USERS_ENDPOINTS.UPDATE_USER_ROLES.path(),
|
|
41335
|
+
service: USERS_ENDPOINTS.UPDATE_USER_ROLES.service,
|
|
41336
|
+
body: {
|
|
41337
|
+
user_id: args.user_id,
|
|
41338
|
+
platform_key: args.platform_key,
|
|
41339
|
+
is_admin: args.is_admin,
|
|
41340
|
+
},
|
|
41341
|
+
method: 'POST',
|
|
41342
|
+
}
|
|
41343
|
+
: {
|
|
41344
|
+
url: USERS_ENDPOINTS.UPDATE_USER_ROLES_EDX.path(),
|
|
41345
|
+
service: USERS_ENDPOINTS.UPDATE_USER_ROLES_EDX.service,
|
|
41346
|
+
body: args,
|
|
41347
|
+
method: 'POST',
|
|
41348
|
+
},
|
|
41198
41349
|
invalidatesTags: [featureTags.PLATFORM_USERS, ...USERS_QUERY_KEYS.GET_USER_ROLES()],
|
|
41199
41350
|
}),
|
|
41200
41351
|
/**
|
|
41201
|
-
*
|
|
41202
|
-
*
|
|
41203
|
-
*
|
|
41204
|
-
*
|
|
41205
|
-
*
|
|
41206
|
-
* someone else's returns 404.
|
|
41207
|
-
*
|
|
41208
|
-
* Defaults to the CMS/Studio host, where course-creation roles are fully
|
|
41209
|
-
* resolvable; pass `useLms` to fall back to the deprecated LMS host.
|
|
41352
|
+
* The signed-in user's course access roles (`course-staff`,
|
|
41353
|
+
* `course-instructor`, ...) plus a global `staff` row when they are edX
|
|
41354
|
+
* staff, from the edX enrollment roles API through DM's proxy. A user with
|
|
41355
|
+
* no roles returns `[]`. Org-wide roles are not listed here: DM holds them
|
|
41356
|
+
* on the user's platform link.
|
|
41210
41357
|
*/
|
|
41211
41358
|
getUserRoles: builder.query({
|
|
41212
|
-
query: (
|
|
41213
|
-
|
|
41214
|
-
|
|
41215
|
-
|
|
41216
|
-
|
|
41217
|
-
}
|
|
41218
|
-
if (email) {
|
|
41219
|
-
params.email = email;
|
|
41220
|
-
}
|
|
41221
|
-
if (userId !== undefined) {
|
|
41222
|
-
params.user_id = userId;
|
|
41223
|
-
}
|
|
41224
|
-
return {
|
|
41225
|
-
url: endpoint.path(),
|
|
41226
|
-
service: useLms ? endpoint.lmsService : endpoint.service,
|
|
41227
|
-
params,
|
|
41228
|
-
};
|
|
41229
|
-
},
|
|
41359
|
+
query: () => ({
|
|
41360
|
+
url: USERS_ENDPOINTS.GET_USER_ROLES.path(),
|
|
41361
|
+
service: USERS_ENDPOINTS.GET_USER_ROLES.service,
|
|
41362
|
+
}),
|
|
41363
|
+
transformResponse: mapEdxUserRoles,
|
|
41230
41364
|
providesTags: USERS_QUERY_KEYS.GET_USER_ROLES(),
|
|
41231
41365
|
}),
|
|
41232
41366
|
getUserMetadataEdx: builder.query({
|
|
@@ -44158,30 +44292,61 @@ const coursesApiSlice = react.createApi({
|
|
|
44158
44292
|
});
|
|
44159
44293
|
const { useGetUserEnrolledCoursesQuery, useLazyGetUserEnrolledCoursesQuery, useGetUserAssignedCoursesQuery, useLazyGetUserAssignedCoursesQuery, useGetLearnerCourseOutlineQuery, useLazyGetLearnerCourseOutlineQuery, } = coursesApiSlice;
|
|
44160
44294
|
|
|
44295
|
+
/** One request through the base query, unwrapped to its payload. */
|
|
44296
|
+
const fetchData = async (args, api, extraOptions) => {
|
|
44297
|
+
const result = await iblFetchBaseQuery(args, api, extraOptions);
|
|
44298
|
+
if (result.error) {
|
|
44299
|
+
throw result.error;
|
|
44300
|
+
}
|
|
44301
|
+
return result.data;
|
|
44302
|
+
};
|
|
44303
|
+
/**
|
|
44304
|
+
* DM's catalog user endpoints take `username=<self>` where the edX plugin
|
|
44305
|
+
* inferred the caller from the JWT; a caller that passes none gets the stored
|
|
44306
|
+
* user.
|
|
44307
|
+
*/
|
|
44308
|
+
const fetchForUser = async (path, params, username, api, extraOptions) => {
|
|
44309
|
+
const user = username !== null && username !== void 0 ? username : (await getStoredUsername());
|
|
44310
|
+
if (!user) {
|
|
44311
|
+
return { error: { status: 400, data: 'No username available' } };
|
|
44312
|
+
}
|
|
44313
|
+
const search = new URLSearchParams({ username: user, ...params });
|
|
44314
|
+
const data = await fetchData({ url: `${path}?${search.toString()}`, service: exports.SERVICES.DM, method: 'GET' }, api, extraOptions);
|
|
44315
|
+
return { data };
|
|
44316
|
+
};
|
|
44161
44317
|
const courseMetadataApiSlice = react.createApi({
|
|
44162
44318
|
reducerPath: 'courseMetadataApiSlice',
|
|
44163
44319
|
baseQuery: iblFetchBaseQuery,
|
|
44164
44320
|
endpoints: (builder) => ({
|
|
44321
|
+
/**
|
|
44322
|
+
* The edX plugin's `course_metadata` payload, served by DM (to signed-in
|
|
44323
|
+
* and anonymous callers alike).
|
|
44324
|
+
*/
|
|
44165
44325
|
getCourseMetaData: builder.query({
|
|
44166
44326
|
query: ({ courseKey }) => ({
|
|
44167
|
-
url: `/
|
|
44168
|
-
service: exports.SERVICES.
|
|
44327
|
+
url: `/v1/courses/metadata?course_id=${encodeURIComponent(courseKey)}`,
|
|
44328
|
+
service: exports.SERVICES.DM,
|
|
44169
44329
|
method: 'GET',
|
|
44170
44330
|
}),
|
|
44331
|
+
// DM leaves the price null where the plugin said "Free".
|
|
44332
|
+
transformResponse: (course) => {
|
|
44333
|
+
var _a;
|
|
44334
|
+
return ({
|
|
44335
|
+
...course,
|
|
44336
|
+
course_price: (_a = course.course_price) !== null && _a !== void 0 ? _a : 'Free',
|
|
44337
|
+
});
|
|
44338
|
+
},
|
|
44171
44339
|
}),
|
|
44340
|
+
/**
|
|
44341
|
+
* The completion outline, from DM. DM answers 404 for a course the user is
|
|
44342
|
+
* not enrolled in, where the edX plugin returned an empty outline.
|
|
44343
|
+
*/
|
|
44172
44344
|
getCourseCompletionOutlines: builder.query({
|
|
44173
|
-
|
|
44174
|
-
url: `/api/ibl/completion/course_outline/${courseKey}?course_id=${encodeURIComponent(courseKey)}`,
|
|
44175
|
-
service: exports.SERVICES.LMS,
|
|
44176
|
-
method: 'GET',
|
|
44177
|
-
}),
|
|
44345
|
+
queryFn: ({ courseKey, username }, api, extraOptions) => fetchForUser('/api/catalog/enrollment/courses/completion/', { course_id: courseKey, endpoint: 'outline' }, username, api, extraOptions),
|
|
44178
44346
|
}),
|
|
44347
|
+
/** Enrollment eligibility, from DM. Same fields as the edX plugin plus `is_eligible`. */
|
|
44179
44348
|
getCourseEligibility: builder.query({
|
|
44180
|
-
|
|
44181
|
-
url: `/api/ibl/enrollment/enroll_status?course_id=${encodeURIComponent(courseKey)}`,
|
|
44182
|
-
service: exports.SERVICES.LMS,
|
|
44183
|
-
method: 'GET',
|
|
44184
|
-
}),
|
|
44349
|
+
queryFn: ({ courseKey, username }, api, extraOptions) => fetchForUser('/api/catalog/eligibility/courses/check/', { course_id: courseKey }, username, api, extraOptions),
|
|
44185
44350
|
}),
|
|
44186
44351
|
createCourseEnrollment: builder.mutation({
|
|
44187
44352
|
query: (body) => ({
|
|
@@ -44224,38 +44389,54 @@ const edxSsoApiSlice = react.createApi({
|
|
|
44224
44389
|
});
|
|
44225
44390
|
const { useGetEdxSSOTokenQuery, useLazyGetEdxSSOTokenQuery } = edxSsoApiSlice;
|
|
44226
44391
|
|
|
44392
|
+
// Endpoints DM's edX proxy admits use `SERVICES.STUDIO` (proxied when
|
|
44393
|
+
// `Config.edxProxyEnabled`). The rest are pinned to the Studio host: the
|
|
44394
|
+
// `api/ibl` manage endpoints the proxy refuses, and the session + CSRF views.
|
|
44227
44395
|
const STUDIO_AUTHORING_ENDPOINTS = {
|
|
44396
|
+
// Course-creator app endpoint, not under `api/ibl` so the proxy reaches it.
|
|
44397
|
+
// Takes `{ org, display_name, number, run }`, answers `{ course_parent_locator }`.
|
|
44228
44398
|
CREATE_COURSE: {
|
|
44229
|
-
path: (
|
|
44399
|
+
path: () => '/api/v1/ibl/course/create',
|
|
44230
44400
|
service: exports.SERVICES.STUDIO,
|
|
44231
44401
|
},
|
|
44402
|
+
// Libraries still go through the plugin's manage endpoint.
|
|
44403
|
+
CREATE_LIBRARY: {
|
|
44404
|
+
path: (contentType) => `/api/ibl/manage/${contentType}/`,
|
|
44405
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44406
|
+
},
|
|
44407
|
+
// TODO(iblai-platform#2707 B5): no DM or proxy replacement for course delete.
|
|
44232
44408
|
DELETE_COURSE: {
|
|
44233
44409
|
path: () => '/api/ibl/manage/course/delete',
|
|
44234
|
-
service: exports.SERVICES.
|
|
44410
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44235
44411
|
},
|
|
44412
|
+
// TODO(iblai-platform#2707 row 13 / B6): the edX half could become a
|
|
44413
|
+
// `contentstore/v1/course_details` PUT through the proxy, but the DM catalog
|
|
44414
|
+
// half (`/api/catalog/metadata/course/`) is DM-admin only, so the combined
|
|
44415
|
+
// plugin endpoint stays until platform admins can write catalog metadata.
|
|
44236
44416
|
COURSE_SETTINGS: {
|
|
44237
44417
|
path: (contentType) => `/api/ibl/v1/${contentType}_settings/`,
|
|
44238
|
-
service: exports.SERVICES.
|
|
44418
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44239
44419
|
},
|
|
44420
|
+
// Session + CSRF views, not JWT-capable: stay on the Studio host.
|
|
44240
44421
|
CREATE_XBLOCK: {
|
|
44241
44422
|
path: () => '/xblock/',
|
|
44242
|
-
service: exports.SERVICES.
|
|
44423
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44243
44424
|
},
|
|
44244
44425
|
UPDATE_XBLOCK: {
|
|
44245
44426
|
path: (locator) => `/xblock/${locator}`,
|
|
44246
|
-
service: exports.SERVICES.
|
|
44427
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44247
44428
|
},
|
|
44248
44429
|
SCORM_SUBMIT: {
|
|
44249
44430
|
path: (locator) => `/xblock/${locator}/handler/studio_submit`,
|
|
44250
|
-
service: exports.SERVICES.
|
|
44431
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44251
44432
|
},
|
|
44252
44433
|
UPLOAD_ASSET: {
|
|
44253
44434
|
path: (courseKey) => `/assets/${courseKey}/`,
|
|
44254
|
-
service: exports.SERVICES.
|
|
44435
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44255
44436
|
},
|
|
44256
44437
|
ADVANCED_SETTINGS: {
|
|
44257
44438
|
path: (courseKey) => `/settings/advanced/${courseKey}`,
|
|
44258
|
-
service: exports.SERVICES.
|
|
44439
|
+
service: exports.SERVICES.STUDIO_DIRECT,
|
|
44259
44440
|
},
|
|
44260
44441
|
VIDEO_UPLOAD: {
|
|
44261
44442
|
path: (courseKey) => `/api/v1/ibl/course/${courseKey}/s3-upload`,
|
|
@@ -44263,23 +44444,88 @@ const STUDIO_AUTHORING_ENDPOINTS = {
|
|
|
44263
44444
|
},
|
|
44264
44445
|
};
|
|
44265
44446
|
|
|
44447
|
+
const RUN_SUFFIX_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
44266
44448
|
/**
|
|
44267
|
-
*
|
|
44268
|
-
*
|
|
44269
|
-
|
|
44449
|
+
* A course number derived from the display name: letters, digits and single
|
|
44450
|
+
* hyphens, at most 30 characters, `COURSE` when nothing usable is left.
|
|
44451
|
+
*/
|
|
44452
|
+
const courseNumberFromDisplayName = (displayName) => {
|
|
44453
|
+
const number = displayName
|
|
44454
|
+
.trim()
|
|
44455
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
44456
|
+
.replace(/^-+|-+$/g, '')
|
|
44457
|
+
.slice(0, 30)
|
|
44458
|
+
.replace(/-+$/, '');
|
|
44459
|
+
return number || 'COURSE';
|
|
44460
|
+
};
|
|
44461
|
+
/** A run like `2026_a1B`: the year plus a short random suffix, as Studio's own generator makes them. */
|
|
44462
|
+
const generateCourseRun = (now = new Date()) => {
|
|
44463
|
+
let suffix = '';
|
|
44464
|
+
for (let index = 0; index < 3; index += 1) {
|
|
44465
|
+
suffix += RUN_SUFFIX_ALPHABET[Math.floor(Math.random() * RUN_SUFFIX_ALPHABET.length)];
|
|
44466
|
+
}
|
|
44467
|
+
return `${now.getFullYear()}_${suffix}`;
|
|
44468
|
+
};
|
|
44469
|
+
/**
|
|
44470
|
+
* The course-creator endpoint needs `number` and `run`, which the old plugin
|
|
44471
|
+
* endpoint generated itself; fill them in when the caller only gave `org` and
|
|
44472
|
+
* `display_name`.
|
|
44473
|
+
*/
|
|
44474
|
+
const withCourseNumberAndRun = (courseData) => {
|
|
44475
|
+
var _a, _b;
|
|
44476
|
+
return ({
|
|
44477
|
+
...courseData,
|
|
44478
|
+
number: (_a = courseData.number) !== null && _a !== void 0 ? _a : courseNumberFromDisplayName(courseData.display_name),
|
|
44479
|
+
run: (_b = courseData.run) !== null && _b !== void 0 ? _b : generateCourseRun(),
|
|
44480
|
+
});
|
|
44481
|
+
};
|
|
44482
|
+
/** `block-v1:ORG+NUM+RUN+type@course+block@course` → `course-v1:ORG+NUM+RUN`. */
|
|
44483
|
+
const courseKeyFromLocator = (locator) => {
|
|
44484
|
+
const match = /^block-v1:(.+)\+type@course\+block@course$/.exec(locator);
|
|
44485
|
+
return match ? `course-v1:${match[1]}` : undefined;
|
|
44486
|
+
};
|
|
44487
|
+
|
|
44488
|
+
/**
|
|
44489
|
+
* Studio (Open edX CMS) course-authoring endpoints. Course creation and video
|
|
44490
|
+
* upload reach Studio through DM's edX proxy when `Config.edxProxyEnabled`;
|
|
44491
|
+
* the rest hit the Studio host (`Config.studioUrl`) with cookie-session auth +
|
|
44492
|
+
* CSRF token, mirroring how Studio's own frontend talks to it.
|
|
44270
44493
|
*/
|
|
44271
44494
|
const studioAuthoringApiSlice = react.createApi({
|
|
44272
44495
|
reducerPath: 'studioAuthoringApiSlice',
|
|
44273
44496
|
baseQuery: iblFetchBaseQuery,
|
|
44274
44497
|
endpoints: (builder) => ({
|
|
44275
|
-
/**
|
|
44498
|
+
/**
|
|
44499
|
+
* Create the course (or library) shell. Returns `course_key`.
|
|
44500
|
+
*
|
|
44501
|
+
* A course goes to the course-creator endpoint, which needs `number` and
|
|
44502
|
+
* `run` (generated from the display name when absent) and answers with the
|
|
44503
|
+
* root block locator; `course_key` is derived from it. A library still uses
|
|
44504
|
+
* the plugin's manage endpoint on the Studio host.
|
|
44505
|
+
*/
|
|
44276
44506
|
createStudioCourse: builder.mutation({
|
|
44277
|
-
query: ({ courseData, contentType = 'course' }) =>
|
|
44278
|
-
|
|
44279
|
-
|
|
44280
|
-
|
|
44281
|
-
|
|
44282
|
-
|
|
44507
|
+
query: ({ courseData, contentType = 'course' }) => contentType === 'course'
|
|
44508
|
+
? {
|
|
44509
|
+
url: STUDIO_AUTHORING_ENDPOINTS.CREATE_COURSE.path(),
|
|
44510
|
+
service: STUDIO_AUTHORING_ENDPOINTS.CREATE_COURSE.service,
|
|
44511
|
+
method: 'POST',
|
|
44512
|
+
body: withCourseNumberAndRun(courseData),
|
|
44513
|
+
}
|
|
44514
|
+
: {
|
|
44515
|
+
url: STUDIO_AUTHORING_ENDPOINTS.CREATE_LIBRARY.path(contentType),
|
|
44516
|
+
service: STUDIO_AUTHORING_ENDPOINTS.CREATE_LIBRARY.service,
|
|
44517
|
+
method: 'POST',
|
|
44518
|
+
body: courseData,
|
|
44519
|
+
},
|
|
44520
|
+
transformResponse: (response) => {
|
|
44521
|
+
var _a;
|
|
44522
|
+
return ({
|
|
44523
|
+
...response,
|
|
44524
|
+
course_key: (_a = response.course_key) !== null && _a !== void 0 ? _a : (typeof response.course_parent_locator === 'string'
|
|
44525
|
+
? courseKeyFromLocator(response.course_parent_locator)
|
|
44526
|
+
: undefined),
|
|
44527
|
+
});
|
|
44528
|
+
},
|
|
44283
44529
|
}),
|
|
44284
44530
|
/** Delete a course — used as rollback when a later creation step fails. */
|
|
44285
44531
|
deleteStudioCourse: builder.mutation({
|
|
@@ -47168,9 +47414,11 @@ const edxProctoringApiSlice = react.createApi({
|
|
|
47168
47414
|
const { useGetExamInfoQuery, useLazyGetExamInfoQuery, useUpdateExamAttemptMutation, useStartExamMutation, } = edxProctoringApiSlice;
|
|
47169
47415
|
|
|
47170
47416
|
const RETIREMENT_ENDPOINTS = {
|
|
47417
|
+
// Retires the caller (no body). Answers 202: the retirement then runs
|
|
47418
|
+
// asynchronously on DM and edX.
|
|
47171
47419
|
SELF_RETIRE: {
|
|
47172
|
-
service: exports.SERVICES.
|
|
47173
|
-
path: () => `/api/
|
|
47420
|
+
service: exports.SERVICES.DM,
|
|
47421
|
+
path: () => `/api/core/users/delete/`,
|
|
47174
47422
|
},
|
|
47175
47423
|
};
|
|
47176
47424
|
|
|
@@ -49950,7 +50198,8 @@ const skillsMiddleware = [
|
|
|
49950
50198
|
userLlmUsageApiSlice.middleware,
|
|
49951
50199
|
];
|
|
49952
50200
|
|
|
49953
|
-
const initializeDataLayer = (dmUrl, lmsUrl, legacyLmsUrl, storageService, httpErrorHandler = {}, studioUrl, refreshAuthToken) => {
|
|
50201
|
+
const initializeDataLayer = (dmUrl, lmsUrl, legacyLmsUrl, storageService, httpErrorHandler = {}, studioUrl, refreshAuthToken, options) => {
|
|
50202
|
+
var _a;
|
|
49954
50203
|
IblDataLayer.storage = storageService;
|
|
49955
50204
|
Config.dmUrl = dmUrl;
|
|
49956
50205
|
Config.lmsUrl = lmsUrl;
|
|
@@ -49960,6 +50209,7 @@ const initializeDataLayer = (dmUrl, lmsUrl, legacyLmsUrl, storageService, httpEr
|
|
|
49960
50209
|
Config.studioUrl = studioUrl;
|
|
49961
50210
|
}
|
|
49962
50211
|
Config.refreshAuthToken = refreshAuthToken;
|
|
50212
|
+
Config.edxProxyEnabled = (_a = options === null || options === void 0 ? void 0 : options.edxProxy) !== null && _a !== void 0 ? _a : false;
|
|
49963
50213
|
};
|
|
49964
50214
|
|
|
49965
50215
|
exports.AGENT_SKILLS_ENDPOINTS = AGENT_SKILLS_ENDPOINTS;
|