From 638e992b657ef8d5e770eea72e68a030e03d7ca0 Mon Sep 17 00:00:00 2001
From: Richard Tibbles
Date: Tue, 11 Apr 2023 09:38:15 -0700
Subject: [PATCH 1/5] Properly handle error responses from remote.
---
kolibri/core/content/api.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/kolibri/core/content/api.py b/kolibri/core/content/api.py
index 9e00b93ddf..ca516d1e5d 100644
--- a/kolibri/core/content/api.py
+++ b/kolibri/core/content/api.py
@@ -154,6 +154,9 @@ def _hande_proxied_request(self, request):
response = requests.get(
remote_url, params=qs, headers=self._get_request_headers(request)
)
+ if response.status_code == 404:
+ raise Http404("Remote resource not found")
+ response.raise_for_status()
# If Etag is set on the response we have returned here, any further Etag will not be modified
# by the django etag decorator, so this should allow us to transparently proxy the remote etag.
try:
From 567d32f44d40297a2dbcf0a2f26d614b05465a6f Mon Sep 17 00:00:00 2001
From: Richard Tibbles
Date: Tue, 11 Apr 2023 09:38:41 -0700
Subject: [PATCH 2/5] Don't proxy Accept-Encoding headers as we need to decode
on the backend.
---
kolibri/core/content/api.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/kolibri/core/content/api.py b/kolibri/core/content/api.py
index ca516d1e5d..1741c0fed6 100644
--- a/kolibri/core/content/api.py
+++ b/kolibri/core/content/api.py
@@ -121,7 +121,8 @@ def _should_proxy_request(self, request):
def _get_request_headers(self, request):
return {
"Accept": request.META.get("HTTP_ACCEPT"),
- "Accept-Encoding": request.META.get("HTTP_ACCEPT_ENCODING"),
+ # Don't proxy client's accept headers as it may include br for brotli
+ # that we cannot rely on having decompression for available on the server.
"Accept-Language": request.META.get("HTTP_ACCEPT_LANGUAGE"),
"Content-Type": request.META.get("CONTENT_TYPE"),
"If-None-Match": request.META.get("HTTP_IF_NONE_MATCH", ""),
From 59970689a9963c634e26509c6e875fd2f100bbf6 Mon Sep 17 00:00:00 2001
From: Richard Tibbles
Date: Tue, 11 Apr 2023 09:39:00 -0700
Subject: [PATCH 3/5] Return fetched device info from the Studio status
endpoint.
---
kolibri/core/content/api.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/kolibri/core/content/api.py b/kolibri/core/content/api.py
index 1741c0fed6..b824a798f2 100644
--- a/kolibri/core/content/api.py
+++ b/kolibri/core/content/api.py
@@ -1725,6 +1725,9 @@ def kolibri_studio_status(self, request, **kwargs):
if resp.status_code == 404:
raise requests.ConnectionError("Kolibri Studio URL is incorrect!")
else:
- return Response({"status": "online"})
+ data = resp.json()
+ data["available"] = True
+ data["status"] = "online"
+ return Response(data)
except requests.ConnectionError:
- return Response({"status": "offline"})
+ return Response({"status": "offline", "available": False})
From 2b4db47cd09ba163230fe5afd8d4a51edbbc2621 Mon Sep 17 00:00:00 2001
From: Richard Tibbles
Date: Tue, 11 Apr 2023 10:39:36 -0700
Subject: [PATCH 4/5] Simplify Studio library display handling.
---
.../assets/src/composables/useDevices.js | 37 ++++++++++++-
.../src/modules/recommended/handlers.js | 45 ++++------------
.../plugins/learn/assets/src/routes/index.js | 4 +-
.../src/views/ExploreLibrariesPage/index.vue | 52 ++++---------------
4 files changed, 57 insertions(+), 81 deletions(-)
diff --git a/kolibri/plugins/learn/assets/src/composables/useDevices.js b/kolibri/plugins/learn/assets/src/composables/useDevices.js
index 6e632dc256..1b015122cf 100644
--- a/kolibri/plugins/learn/assets/src/composables/useDevices.js
+++ b/kolibri/plugins/learn/assets/src/composables/useDevices.js
@@ -3,19 +3,52 @@
*/
import { computed, getCurrentInstance, ref } from 'kolibri.lib.vueCompositionApi';
-import { NetworkLocationResource } from 'kolibri.resources';
+import { NetworkLocationResource, RemoteChannelResource } from 'kolibri.resources';
import { get, set } from '@vueuse/core';
+import useMinimumKolibriVersion from 'kolibri.coreVue.composables.useMinimumKolibriVersion';
+import { KolibriStudioId } from '../constants';
+import { learnStrings } from '../views/commonLearnStrings';
+import plugin_data from 'plugin_data';
// The refs are defined in the outer scope so they can be used as a shared store
const currentDevice = ref(null);
+const KolibriStudioDeviceData = {
+ id: KolibriStudioId,
+ instance_id: KolibriStudioId,
+ base_url: plugin_data.studio_baseurl,
+ get device_name() {
+ return learnStrings.$tr('kolibriLibrary');
+ },
+};
+
+const { isMinimumKolibriVersion } = useMinimumKolibriVersion(0, 16, 0);
+
function fetchDevices() {
- return NetworkLocationResource.list().then(devices => {
+ return Promise.all([
+ RemoteChannelResource.getKolibriStudioStatus(),
+ NetworkLocationResource.list(),
+ ]).then(([studioResponse, devices]) => {
+ const studio = studioResponse.data;
+ devices = devices.filter(device => isMinimumKolibriVersion(device.kolibri_version));
+ if (studio.available && isMinimumKolibriVersion(studio.kolibri_version || '0.15.0')) {
+ return [
+ {
+ ...studio,
+ ...KolibriStudioDeviceData,
+ },
+ ...devices,
+ ];
+ }
return devices;
});
}
export function setCurrentDevice(id) {
+ if (id === KolibriStudioId) {
+ set(currentDevice, KolibriStudioDeviceData);
+ return Promise.resolve(KolibriStudioDeviceData);
+ }
return NetworkLocationResource.fetchModel({ id }).then(device => {
set(currentDevice, device);
return device;
diff --git a/kolibri/plugins/learn/assets/src/modules/recommended/handlers.js b/kolibri/plugins/learn/assets/src/modules/recommended/handlers.js
index c3c5505c29..cfe1373713 100644
--- a/kolibri/plugins/learn/assets/src/modules/recommended/handlers.js
+++ b/kolibri/plugins/learn/assets/src/modules/recommended/handlers.js
@@ -1,12 +1,11 @@
import { get } from '@vueuse/core';
-import { ContentNodeResource, RemoteChannelResource } from 'kolibri.resources';
+import { ContentNodeResource } from 'kolibri.resources';
import samePageCheckGenerator from 'kolibri.utils.samePageCheckGenerator';
-import { PageNames, KolibriStudioId } from '../../constants';
+import { PageNames } from '../../constants';
import useChannels from '../../composables/useChannels';
import { setCurrentDevice } from '../../composables/useDevices';
import useLearnerResources from '../../composables/useLearnerResources';
import { searchKeys } from '../../composables/useSearch';
-import plugin_data from 'plugin_data';
const { channels, fetchChannels } = useChannels();
@@ -78,39 +77,13 @@ function _showLibrary(store, query, channels, baseurl) {
}
export function showLibrary(store, query, deviceId = null) {
- /**
- * ToDo: remove if block.
- * Currently the channels & contentnode browser apis in studio
- * are not able to load content using the the studio base url.
- * Once studio is updated, this function will need to be refactored
- * to use the else block code only.
- *
- * The if block is meant for UI viualization purposes only
- * during development
- */
- if (deviceId === KolibriStudioId) {
- RemoteChannelResource.getKolibriStudioStatus().then(({ data }) => {
- if (data.status === 'online') {
- RemoteChannelResource.fetchCollection().then(channels => {
- //This is a hack to return kolibri channels.
- store.commit('SET_ROOT_NODES', channels);
-
- store.commit('CORE_SET_PAGE_LOADING', false);
- store.commit('CORE_SET_ERROR', null);
- store.commit('SET_PAGE_NAME', PageNames.LIBRARY);
- return Promise.resolve();
- });
- }
- });
- } else {
- if (deviceId) {
- return setCurrentDevice(deviceId).then(device => {
- const baseurl = deviceId === KolibriStudioId ? plugin_data.studio_baseurl : device.base_url;
- return fetchChannels({ baseurl }).then(channels => {
- return _showLibrary(store, query, channels, baseurl);
- });
+ if (deviceId) {
+ return setCurrentDevice(deviceId).then(device => {
+ const baseurl = device.base_url;
+ return fetchChannels({ baseurl }).then(channels => {
+ return _showLibrary(store, query, channels, baseurl);
});
- }
- return _showLibrary(store, query, get(channels));
+ });
}
+ return _showLibrary(store, query, get(channels));
}
diff --git a/kolibri/plugins/learn/assets/src/routes/index.js b/kolibri/plugins/learn/assets/src/routes/index.js
index b354a3b3b9..2581572e85 100644
--- a/kolibri/plugins/learn/assets/src/routes/index.js
+++ b/kolibri/plugins/learn/assets/src/routes/index.js
@@ -9,7 +9,7 @@ import { setClasses, setResumableContentNodes } from '../composables/useLearnerR
import { setContentNodeProgress } from '../composables/useContentNodeProgress';
import { showTopicsTopic, showTopicsContent } from '../modules/topicsTree/handlers';
import { showLibrary } from '../modules/recommended/handlers';
-import { PageNames, ClassesPageNames } from '../constants';
+import { PageNames, ClassesPageNames, KolibriStudioId } from '../constants';
import LibraryPage from '../views/LibraryPage';
import HomePage from '../views/HomePage';
import TopicsPage from '../views/TopicsPage';
@@ -45,7 +45,7 @@ function hydrateHomePage() {
});
}
-const optionalDeviceIdPathSegment = '/:deviceId([a-f0-9]{32}|kolibri-studio)?';
+const optionalDeviceIdPathSegment = `/:deviceId([a-f0-9]{32}|${KolibriStudioId})?`;
export default [
{
diff --git a/kolibri/plugins/learn/assets/src/views/ExploreLibrariesPage/index.vue b/kolibri/plugins/learn/assets/src/views/ExploreLibrariesPage/index.vue
index a4cd0f251f..ead9689689 100644
--- a/kolibri/plugins/learn/assets/src/views/ExploreLibrariesPage/index.vue
+++ b/kolibri/plugins/learn/assets/src/views/ExploreLibrariesPage/index.vue
@@ -16,17 +16,6 @@
{{ $tr('showingLibraries') }}
-
@@ -54,7 +43,7 @@
:deviceIcon="getDeviceIcon(device)"
:channels="device.channels"
:totalChannels="device['total_channels']"
- :pinIcon="getPinIcon(isPinned(device['instance_id']))"
+ :pinIcon="getPinIcon(false)"
@togglePin="handlePinToggle"
/>
0 && this.moreDevices.length < this.unpinnedDevices?.length
);
},
- kolibriStudioId() {
- return KolibriStudioId;
- },
networkDevicesWithChannels() {
- return this.networkDevices.filter(device => device.channels?.length > 0);
+ return this.networkDevices.filter(
+ device =>
+ device.channels?.length > 0 &&
+ (device.instance_id !== KolibriStudioId || this.isSuperuser)
+ );
},
pageHeaderStyle() {
return {
@@ -150,12 +136,12 @@
},
pinnedDevices() {
return this.networkDevicesWithChannels.filter(netdev => {
- return this.usersPinsDeviceIds.includes(netdev.instance_id);
+ return (
+ this.usersPinsDeviceIds.includes(netdev.instance_id) ||
+ netdev.instance_id === KolibriStudioId
+ );
});
},
- showKolibriLibrary() {
- return this.isSuperuser && this.isKolibriLibraryLoaded;
- },
unpinnedDevices() {
return this.networkDevicesWithChannels.filter(netdev => {
return !this.usersPinsDeviceIds.includes(netdev.instance_id);
@@ -171,19 +157,6 @@
});
});
- RemoteChannelResource.getKolibriStudioStatus().then(({ data }) => {
- if (data.status === 'online') {
- RemoteChannelResource.fetchCollection()
- .then(channels => {
- this.isKolibriLibraryLoaded = true;
- this.kolibriLibraryChannels = channels.slice(0, 4);
- this.totalChannels = channels.length;
- })
- .catch(() => {
- this.isKolibriLibraryLoaded = true;
- });
- }
- });
this.fetchDevices().then(devices => {
this.networkDevices = devices;
for (const device of this.networkDevices) {
@@ -199,9 +172,6 @@
});
},
methods: {
- isPinned(instance_id) {
- return this.usersPinsDeviceIds.includes(instance_id);
- },
createPin(instance_id) {
return this.createPinForUser(instance_id).then(response => {
const id = response.id;
From 5001d3544b7985ab03375581285a4a4642c9177b Mon Sep 17 00:00:00 2001
From: Richard Tibbles
Date: Tue, 11 Apr 2023 10:40:06 -0700
Subject: [PATCH 5/5] Add navigation guards for explore libraries page to
prevent access when not logged in.
---
kolibri/plugins/learn/assets/src/routes/index.js | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/kolibri/plugins/learn/assets/src/routes/index.js b/kolibri/plugins/learn/assets/src/routes/index.js
index 2581572e85..f1e2f922c9 100644
--- a/kolibri/plugins/learn/assets/src/routes/index.js
+++ b/kolibri/plugins/learn/assets/src/routes/index.js
@@ -205,6 +205,15 @@ export default [
name: PageNames.EXPLORE_LIBRARIES,
path: '/explore_libraries',
component: ExploreLibrariesPage,
+ handler: () => {
+ if (!get(isUserLoggedIn)) {
+ router.replace({ name: PageNames.LIBRARY });
+ return;
+ }
+ if (unassignedContentGuard()) {
+ return unassignedContentGuard();
+ }
+ },
},
{
path: '*',