diff --git a/client/index.html b/client/index.html index 31fdea1c..75616849 100644 --- a/client/index.html +++ b/client/index.html @@ -7,7 +7,7 @@ - + myVBV diff --git a/client/src/services/__tests__/request_learning_path_json.py b/client/src/services/__tests__/request_learning_path_json.py index 20088cb7..023dcf9b 100644 --- a/client/src/services/__tests__/request_learning_path_json.py +++ b/client/src/services/__tests__/request_learning_path_json.py @@ -8,7 +8,7 @@ def main(): client.get('http://localhost:8000/') client.post( - 'http://localhost:8000/core/login/', + 'http://localhost:8000/api/core/login/', json={ 'username': 'admin', 'password': 'test', @@ -16,7 +16,7 @@ def main(): ) response = client.get( - 'http://localhost:8000/learnpath/api/page/unit-test-lernpfad/', + 'http://localhost:8000/api/learnpath/page/unit-test-lernpfad/', ) print(response.status_code) print(response.json()) diff --git a/client/src/stores/learningPath.ts b/client/src/stores/learningPath.ts index ec3930c2..b2e3b598 100644 --- a/client/src/stores/learningPath.ts +++ b/client/src/stores/learningPath.ts @@ -19,13 +19,13 @@ export const useLearningPathStore = defineStore({ if (this.learningPath && !reload) { return this.learningPath; } - const learningPathData = await itGet(`/learnpath/api/page/${slug}/`); + const learningPathData = await itGet(`/api/learnpath/page/${slug}/`); const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`); if (!learningPathData) { throw `No learning path found with: ${slug}`; } - + this.learningPath = LearningPath.fromJson(learningPathData, completionData); return this.learningPath; }, diff --git a/client/src/stores/user.ts b/client/src/stores/user.ts index 8097dba7..74bdbfb5 100644 --- a/client/src/stores/user.ts +++ b/client/src/stores/user.ts @@ -1,8 +1,8 @@ -import * as log from 'loglevel'; +import * as log from "loglevel"; -import {defineStore} from 'pinia' -import {itGet, itPost} from '@/fetchHelpers'; -import {useAppStore} from '@/stores/app'; +import { defineStore } from "pinia"; +import { itGet, itPost } from "@/fetchHelpers"; +import { useAppStore } from "@/stores/app"; // typed state https://stackoverflow.com/questions/71012513/when-using-pinia-and-typescript-how-do-you-use-an-action-to-set-the-state export type UserState = { @@ -34,7 +34,7 @@ export const useUserStore = defineStore({ actions: { handleLogin(username: string, password: string, next='/') { if (username && password) { - itPost('/core/login/', { + itPost('/api/core/login/', { username, password, }).then((data) => { @@ -49,7 +49,7 @@ export const useUserStore = defineStore({ } }, handleLogout() { - itPost('/core/logout/', {}) + itPost('/api/core/logout/', {}) .then(data => { Object.assign(this, initialUserState); window.location.href = '/'; diff --git a/client/src/views/StyleGuideView.vue b/client/src/views/StyleGuideView.vue index db299bff..7008c374 100644 --- a/client/src/views/StyleGuideView.vue +++ b/client/src/views/StyleGuideView.vue @@ -190,7 +190,7 @@ function log(data: any) { -
+
ls-network big
diff --git a/cypress/e2e/helpers.js b/cypress/e2e/helpers.js index 362b9496..dbafa9e0 100644 --- a/cypress/e2e/helpers.js +++ b/cypress/e2e/helpers.js @@ -1,7 +1,7 @@ export const login = (username, password) => { cy.request({ method: 'POST', - url: '/core/login/', + url: '/api/core/login/', body: { username, password }, }) } diff --git a/server/config/api_router.py b/server/config/api_router.py deleted file mode 100644 index 2e053e30..00000000 --- a/server/config/api_router.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.conf import settings -from rest_framework.routers import DefaultRouter, SimpleRouter - -if settings.DEBUG: - router = DefaultRouter() -else: - router = SimpleRouter() - - -app_name = "api" -urlpatterns = router.urls diff --git a/server/config/settings/base.py b/server/config/settings/base.py index 8be507d0..46b932d6 100644 --- a/server/config/settings/base.py +++ b/server/config/settings/base.py @@ -78,7 +78,6 @@ THIRD_PARTY_APPS = [ "rest_framework.authtoken", "corsheaders", "drf_spectacular", - "django_htmx", 'wagtail.contrib.forms', 'wagtail.contrib.redirects', @@ -288,7 +287,7 @@ EMAIL_TIMEOUT = 5 # ADMIN # ------------------------------------------------------------------------------ # Django Admin URL. -ADMIN_URL = "admin/" +ADMIN_URL = "server/admin/" # https://docs.djangoproject.com/en/dev/ref/settings/#admins ADMINS = [("""Daniel Egger""", "info@iterativ.ch")] # https://docs.djangoproject.com/en/dev/ref/settings/#managers @@ -454,7 +453,17 @@ REST_FRAMEWORK = { CORS_URLS_REGEX = r"^/api/.*$" # django-csp -CSP_DEFAULT_SRC = ("'self'", "'unsafe-inline'", 'ws://localhost:5173', 'localhost:8000', 'blob:', 'data:', 'http://*') +CSP_DEFAULT_SRC = [ + "'self'", + "'unsafe-inline'", + 'ws://localhost:5173', + 'ws://127.0.0.1:5173', + 'localhost:8000', + 'localhost:8001', + 'blob:', + 'data:', + 'http://*' +] CSP_FRAME_ANCESTORS = ("'self'",) # By Default swagger ui is available only to admin user. You can change permission classs to change that diff --git a/server/config/urls.py b/server/config/urls.py index 3664a946..ba97a6b9 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -5,19 +5,19 @@ from django.contrib.auth.decorators import user_passes_test from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import include, path, re_path from django.views import defaults as default_views -from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView from ratelimit.exceptions import Ratelimited -from rest_framework.authtoken.views import obtain_auth_token from wagtail import urls as wagtail_urls from wagtail.admin import urls as wagtailadmin_urls from wagtail.documents import urls as wagtaildocs_urls +from vbv_lernwelt.completion.views import request_learning_path_completion, request_circle_completion, \ + mark_circle_completion from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt from vbv_lernwelt.core.views import ( rate_limit_exceeded_view, permission_denied_view, - check_rate_limit, cypress_reset_view, vue_home, vue_login, me_user_view, vue_logout, ) -from .wagtail_api import wagtail_api_router + check_rate_limit, cypress_reset_view, vue_home, vue_login, me_user_view, vue_logout, generate_web_component_icons, ) +from vbv_lernwelt.learnpath.views import page_api_view def raise_example_error(request): @@ -31,39 +31,43 @@ def raise_example_error(request): # fmt: off urlpatterns = [ - path('admin/raise_error/', user_passes_test(lambda u: u.is_superuser, login_url='/login/')(raise_example_error), ), path(settings.ADMIN_URL, admin.site.urls), - path("checkratelimit/", check_rate_limit), + + # wagtail urls + path('server/cms/', include(wagtailadmin_urls)), + path('server/documents/', include(wagtaildocs_urls)), + path('server/pages/', include(wagtail_urls)), + + # user management path("sso/", include("vbv_lernwelt.sso.urls")), - path('cms/', include(wagtailadmin_urls)), - path('documents/', include(wagtaildocs_urls)), - path('pages/', include(wagtail_urls)), - path('learnpath/', include("vbv_lernwelt.learnpath.urls")), - path('api/completion/', include("vbv_lernwelt.completion.urls")), re_path(r'api/core/me/$', me_user_view, name='me_user_view'), - re_path(r'core/login/$', django_view_authentication_exempt(vue_login), name='vue_login'), - re_path(r'core/logout/$', vue_logout, name='vue_logout'), -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + re_path(r'api/core/login/$', django_view_authentication_exempt(vue_login), name='vue_login'), + re_path(r'api/core/logout/$', vue_logout, name='vue_logout'), + + # core + re_path(r"server/core/icons/$", generate_web_component_icons, name="generate_web_component_icons"), + + # learnpath + path(r"api/learnpath/page//", page_api_view, name="page_api_view"), + + # completion + path(r"api/completion/circle//", request_circle_completion, name="request_circle_completion"), + path(r"api/completion/learning_path//", request_learning_path_completion, name="request_learning_path_completion"), + path(r"api/completion/circle/mark/", mark_circle_completion, name="mark_circle_completion"), + + # testing and debug + path('server/raise_error/', user_passes_test(lambda u: u.is_superuser, login_url='/login/')(raise_example_error), ), + path("server/checkratelimit/", check_rate_limit), +] +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: # Static file serving when using Gunicorn + Uvicorn for local web socket development urlpatterns += staticfiles_urlpatterns() -# API URLS -urlpatterns += [ - # API base url - path("api/", include("config.api_router")), - path('wagtailapi/v2/', wagtail_api_router.urls), - - # DRF auth token - path("auth-token/", obtain_auth_token), - path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"), - path("api/docs/", SpectacularSwaggerView.as_view(url_name="api-schema"), name="api-docs",), -] - if settings.APP_ENVIRONMENT != 'production': urlpatterns += [ - re_path(r'core/cypressreset/$', cypress_reset_view, name='cypress_reset_view'), + re_path(r'api/core/cypressreset/$', cypress_reset_view, name='cypress_reset_view'), ] # fmt: on @@ -107,4 +111,4 @@ if settings.DEBUG: # serve everything else via the vue app -urlpatterns += [re_path(r'^.*$', vue_home, name='home')] +urlpatterns += [re_path(r'^(?!.*(server/|api/|sso/)).*$', vue_home, name='home')] diff --git a/server/config/wagtail_api.py b/server/config/wagtail_api.py deleted file mode 100644 index d12d1b50..00000000 --- a/server/config/wagtail_api.py +++ /dev/null @@ -1,16 +0,0 @@ - -from wagtail.api.v2.router import WagtailAPIRouter -from wagtail.api.v2.views import PagesAPIViewSet -from wagtail.documents.api.v2.views import DocumentsAPIViewSet -from wagtail.images.api.v2.views import ImagesAPIViewSet - -# Create the router. "wagtailapi" is the URL namespace -wagtail_api_router = WagtailAPIRouter('wagtailapi') - -# Add the three endpoints using the "register_endpoint" method. -# The first parameter is the name of the endpoint (eg. pages, images). This -# is used in the URL of the endpoint -# The second parameter is the endpoint class that handles the requests -wagtail_api_router.register_endpoint('pages', PagesAPIViewSet) -wagtail_api_router.register_endpoint('images', ImagesAPIViewSet) -wagtail_api_router.register_endpoint('documents', DocumentsAPIViewSet) diff --git a/server/integration_tests/ratelimit/test_ratelimit.py b/server/integration_tests/ratelimit/test_ratelimit.py index 20e9c0a8..66cffadc 100644 --- a/server/integration_tests/ratelimit/test_ratelimit.py +++ b/server/integration_tests/ratelimit/test_ratelimit.py @@ -3,7 +3,7 @@ from django.test import TestCase, override_settings class RateLimitTest(TestCase): def setUp(self): - self.url = "/checkratelimit/" + self.url = "/server/checkratelimit/" @override_settings(RATELIMIT_ENABLE=True) def test_checkView_rateLimitAfter5Requests(self): diff --git a/server/requirements/requirements-dev.txt b/server/requirements/requirements-dev.txt index 4f5f0a8f..2fc1305d 100644 --- a/server/requirements/requirements-dev.txt +++ b/server/requirements/requirements-dev.txt @@ -113,8 +113,6 @@ django-extensions==3.1.5 # via -r requirements-dev.in django-filter==21.1 # via wagtail -django-htmx==1.9.0 - # via -r requirements.in django-ipware==4.0.2 # via -r requirements.in django-model-utils==4.2.0 diff --git a/server/requirements/requirements.in b/server/requirements/requirements.in index 700df65a..186896df 100644 --- a/server/requirements/requirements.in +++ b/server/requirements/requirements.in @@ -19,7 +19,6 @@ djangorestframework # https://github.com/encode/django-rest-framework django-cors-headers # https://github.com/adamchainz/django-cors-headers # DRF-spectacular for api documentation drf-spectacular -django-htmx dj-database-url django-click django-ratelimit diff --git a/server/requirements/requirements.txt b/server/requirements/requirements.txt index 4f09972f..b3e71681 100644 --- a/server/requirements/requirements.txt +++ b/server/requirements/requirements.txt @@ -71,8 +71,6 @@ django-csp==3.7 # via -r requirements.in django-filter==21.1 # via wagtail -django-htmx==1.9.0 - # via -r requirements.in django-ipware==4.0.2 # via -r requirements.in django-model-utils==4.2.0 diff --git a/server/vbv_lernwelt/completion/urls.py b/server/vbv_lernwelt/completion/urls.py deleted file mode 100644 index 891712bf..00000000 --- a/server/vbv_lernwelt/completion/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path - -from vbv_lernwelt.completion.views import request_circle_completion, mark_circle_completion, \ - request_learning_path_completion - -urlpatterns = [ - path(r"circle//", request_circle_completion, name="request_circle_completion"), - path(r"learning_path//", request_learning_path_completion, name="request_learning_path_completion"), - path(r"circle/mark/", mark_circle_completion, name="mark_circle_completion"), -] diff --git a/server/vbv_lernwelt/core/views.py b/server/vbv_lernwelt/core/views.py index 7fbaf728..dd647446 100644 --- a/server/vbv_lernwelt/core/views.py +++ b/server/vbv_lernwelt/core/views.py @@ -1,4 +1,6 @@ # Create your views here. +import glob +from pathlib import Path import requests import structlog @@ -23,9 +25,7 @@ logger = structlog.get_logger(__name__) @django_view_authentication_exempt @ensure_csrf_cookie - - -def vue_home(request): +def vue_home(request, *args): if settings.IT_SERVE_VUE: try: res = requests.get(f'{settings.IT_SERVE_VUE_URL}{request.get_full_path()}') @@ -114,3 +114,24 @@ def cypress_reset_view(request): call_command('cypress_reset') return HttpResponseRedirect('/admin/') + + +@django_view_authentication_exempt +def generate_web_component_icons(request): + svg_files = [] + for filepath in glob.iglob(f'{settings.APPS_DIR}/static/icons/*.svg'): + with open(filepath, 'r') as f: + filename = Path(filepath).stem + elementname = 'it-' + filename + svg_files.append({ + 'filepath': filepath, + 'content': f.read(), + 'filename': filename, + 'elementname': elementname, + 'classname': filename.replace('-', '_'), + }) + return render( + request, "core/icons.html", + context={'svg_files': svg_files}, + content_type="application/javascript" + ) diff --git a/server/vbv_lernwelt/learnpath/tests/test_api.py b/server/vbv_lernwelt/learnpath/tests/test_api.py index 1bfd83eb..7fa29ae0 100644 --- a/server/vbv_lernwelt/learnpath/tests/test_api.py +++ b/server/vbv_lernwelt/learnpath/tests/test_api.py @@ -18,7 +18,7 @@ class TestRetrieveLearingPathContents(APITestCase): def test_get_learnpathPage(self): learning_path = LearningPath.objects.get(slug='unit-test-lernpfad') - response = self.client.get('/learnpath/api/page/unit-test-lernpfad/') + response = self.client.get('/api/learnpath/page/unit-test-lernpfad/') print(response) self.assertEqual(response.status_code, 200) data = response.json() diff --git a/server/vbv_lernwelt/learnpath/urls.py b/server/vbv_lernwelt/learnpath/urls.py deleted file mode 100644 index 2d12205e..00000000 --- a/server/vbv_lernwelt/learnpath/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path, re_path - -from .views import generate_web_component_icons, page_api_view - -urlpatterns = [ - path(r"api/page//", page_api_view, name="page_api_view"), - re_path(r"icons/$", generate_web_component_icons, name="generate_web_component_icons"), -] diff --git a/server/vbv_lernwelt/learnpath/views.py b/server/vbv_lernwelt/learnpath/views.py index 590bbd66..2e6856be 100644 --- a/server/vbv_lernwelt/learnpath/views.py +++ b/server/vbv_lernwelt/learnpath/views.py @@ -1,17 +1,11 @@ # Create your views here. -import glob -from pathlib import Path import structlog -from django.conf import settings -from django.shortcuts import render from django.views.decorators.cache import cache_page from rest_framework.decorators import api_view from rest_framework.response import Response from wagtail.models import Page -from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt - logger = structlog.get_logger(__name__) @@ -27,22 +21,3 @@ def page_api_view(request, slug): return Response({"error": str(e)}, status=404) -@django_view_authentication_exempt -def generate_web_component_icons(request): - svg_files = [] - for filepath in glob.iglob(f'{settings.APPS_DIR}/static/icons/*.svg'): - with open(filepath, 'r') as f: - filename = Path(filepath).stem - elementname = 'it-' + filename - svg_files.append({ - 'filepath': filepath, - 'content': f.read(), - 'filename': filename, - 'elementname': elementname, - 'classname': filename.replace('-', '_'), - }) - return render( - request, "learnpath/icons.html", - context={'svg_files': svg_files}, - content_type="application/javascript" - ) diff --git a/server/vbv_lernwelt/templates/base.html b/server/vbv_lernwelt/templates/base.html index 95f38e68..ea9f3d72 100644 --- a/server/vbv_lernwelt/templates/base.html +++ b/server/vbv_lernwelt/templates/base.html @@ -19,8 +19,7 @@ {# Placed at the top of the document so pages load faster with defer #} - - + {% block javascript %} diff --git a/server/vbv_lernwelt/templates/learnpath/icons.html b/server/vbv_lernwelt/templates/core/icons.html similarity index 100% rename from server/vbv_lernwelt/templates/learnpath/icons.html rename to server/vbv_lernwelt/templates/core/icons.html diff --git a/server/vbv_lernwelt/templates/wagtailadmin/pages/index.html b/server/vbv_lernwelt/templates/wagtailadmin/pages/index.html index 02a8b599..a57c29ae 100644 --- a/server/vbv_lernwelt/templates/wagtailadmin/pages/index.html +++ b/server/vbv_lernwelt/templates/wagtailadmin/pages/index.html @@ -17,7 +17,7 @@ - +