Merge remote-tracking branch 'origin/dev' into chore/login

# Conflicts:
#	backend/.env.example
#	backend/package.json
#	backend/src/app.ts
#	backend/src/routes/login.ts
#	backend/src/routes/student.ts
#	docker-compose.yml
#	frontend/src/App.vue
#	frontend/src/views/HomePage.vue
#	frontend/src/views/LoginPage.vue
#	package-lock.json
This commit is contained in:
Gerald Schmittinger 2025-03-09 23:42:38 +01:00
commit de0199de96
109 changed files with 3789 additions and 1727 deletions

View file

@ -1,8 +1,8 @@
import { test, expect } from '@playwright/test';
import { test, expect } from "@playwright/test";
// See here how to get started:
// https://playwright.dev/docs/intro
test('visits the app root url', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toHaveText('You did it!');
test("visits the app root url", async ({ page }) => {
await page.goto("/");
await expect(page.locator("h1")).toHaveText("You did it!");
});

View file

@ -1,12 +1,9 @@
import pluginVue from 'eslint-plugin-vue';
import {
defineConfigWithVueTs,
vueTsConfigs,
} from '@vue/eslint-config-typescript';
import pluginVitest from '@vitest/eslint-plugin';
import pluginPlaywright from 'eslint-plugin-playwright';
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
import rootConfig from '../eslint.config';
import pluginVue from "eslint-plugin-vue";
import { defineConfigWithVueTs, vueTsConfigs } from "@vue/eslint-config-typescript";
import pluginVitest from "@vitest/eslint-plugin";
import pluginPlaywright from "eslint-plugin-playwright";
import skipFormatting from "@vue/eslint-config-prettier/skip-formatting";
import rootConfig from "../eslint.config";
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// Import { configureVueProject } from '@vue/eslint-config-typescript'
@ -15,31 +12,31 @@ import rootConfig from '../eslint.config';
const vueConfig = defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
name: "app/files-to-lint",
files: ["**/*.{ts,mts,tsx,vue}"],
rules: {
"no-useless-assignment": "off", // Depend on `no-unused-vars` to catch this
},
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
name: "app/files-to-ignore",
ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
},
pluginVue.configs['flat/essential'],
pluginVue.configs["flat/essential"],
vueTsConfigs.recommended,
{
...pluginVitest.configs.recommended,
files: ['src/**/__tests__/*'],
files: ["src/**/__tests__/*"],
},
{
...pluginPlaywright.configs['flat/recommended'],
files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'],
...pluginPlaywright.configs["flat/recommended"],
files: ["e2e/**/*.{test,spec}.{js,ts,jsx,tsx}"],
},
skipFormatting
skipFormatting,
);
export default [
...rootConfig,
...vueConfig
]
export default [...rootConfig, ...vueConfig];

View file

@ -2,12 +2,21 @@
<html lang="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="icon"
href="/favicon.ico"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script
type="module"
src="/src/main.ts"
></script>
</body>
</html>

View file

@ -1,5 +1,5 @@
import process from 'node:process';
import { defineConfig, devices } from '@playwright/test';
import process from "node:process";
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
@ -11,7 +11,7 @@ import { defineConfig, devices } from '@playwright/test';
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
testDir: "./e2e",
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
@ -28,18 +28,16 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.CI
? 'http://localhost:4173'
: 'http://localhost:5173',
baseURL: process.env.CI ? "http://localhost:4173" : "http://localhost:5173",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
trace: "on-first-retry",
/* Only on CI systems run the tests headless */
headless: Boolean(process.env.CI),
@ -48,21 +46,21 @@ export default defineConfig({
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
name: "chromium",
use: {
...devices['Desktop Chrome'],
...devices["Desktop Chrome"],
},
},
{
name: 'firefox',
name: "firefox",
use: {
...devices['Desktop Firefox'],
...devices["Desktop Firefox"],
},
},
{
name: 'webkit',
name: "webkit",
use: {
...devices['Desktop Safari'],
...devices["Desktop Safari"],
},
},
@ -105,7 +103,7 @@ export default defineConfig({
* Use the preview server on CI for more realistic testing.
* Playwright will re-use the local server if there is already a dev-server running.
*/
command: process.env.CI ? 'npm run preview' : 'npm run dev',
command: process.env.CI ? "npm run preview" : "npm run dev",
port: process.env.CI ? 4173 : 5173,
reuseExistingServer: !process.env.CI,
},

View file

@ -2,10 +2,10 @@
* @type {import("prettier").Options}
*/
const rootConfig = import ('../prettier.config.js');
const rootConfig = import("../prettier.config.js");
export default {
...rootConfig,
vueIndentScriptAndStyle: true,
singleAttributePerLine: true
singleAttributePerLine: true,
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View file

@ -4,9 +4,7 @@
</script>
<template>
<router-view/>
<router-view />
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,9 @@
<script setup lang="ts">
// This component contains a list with all themes and will be shown on a student's and teacher's homepage.
// This component contains a list with all themes and will be shown on a student's and teacher's homepage.
</script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<b>404 - Page Not Found</b>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,4 +1,4 @@
import {createRouter, createWebHistory} from "vue-router";
import { createRouter, createWebHistory } from "vue-router";
import MenuBar from "@/components/MenuBar.vue";
import StudentHomepage from "@/views/StudentHomepage.vue";
import StudentAssignments from "@/views/assignments/StudentAssignments.vue";
@ -23,12 +23,12 @@ const router = createRouter({
{
path: "/",
name: "home",
component: () => {return import("../views/HomePage.vue")},
component: () => import("../views/HomePage.vue"),
},
{
path: "/login",
name: "LoginPage",
component: () => {return import("../views/LoginPage.vue")}
component: () => import("../views/LoginPage.vue"),
},
{
path: "/callback",
@ -41,24 +41,24 @@ const router = createRouter({
{
path: "home",
name: "StudentHomePage",
component: StudentHomepage
component: StudentHomepage,
},
{
path: "assignment",
name: "StudentAssignments",
component: StudentAssignments
component: StudentAssignments,
},
{
path: "class",
name: "StudentClasses",
component: StudentClasses
component: StudentClasses,
},
{
path: "discussion",
name: "StudentDiscussions",
component: StudentDiscussions
component: StudentDiscussions,
},
]
],
},
{
@ -68,56 +68,54 @@ const router = createRouter({
{
path: "home",
name: "TeacherHomepage",
component: TeacherHomepage
component: TeacherHomepage,
},
{
path: "assignment",
name: "TeacherAssignments",
component: TeacherAssignments
component: TeacherAssignments,
},
{
path: "class",
name: "TeacherClasses",
component: TeacherClasses
component: TeacherClasses,
},
{
path: "discussion",
name: "TeacherDiscussions",
component: TeacherDiscussions
component: TeacherDiscussions,
},
]
],
},
{
path: "/assignment/create",
name: "CreateAssigment",
component: CreateAssignment
component: CreateAssignment,
},
{
path: "/assignment/:id",
name: "SingleAssigment",
component: SingleAssignment
component: SingleAssignment,
},
{
path: "/class/create",
name: "CreateClass",
component: CreateClass
component: CreateClass,
},
{
path: "/class/:id",
name: "SingleClass",
component: SingleClass
component: SingleClass,
},
{
path: "/discussion/create",
name: "CreateDiscussion",
component: CreateDiscussion
component: CreateDiscussion,
},
{
path: "/discussion/:id",
name: "SingleDiscussion",
component: SingleDiscussion
component: SingleDiscussion,
},
{
path: "/:catchAll(.*)",

View file

@ -0,0 +1,18 @@
/**
* Converts a Base64 string to a valid image source URL.
*
* @param base64String - The "image" field from the learning path JSON response.
* @returns A properly formatted data URL for use in an <img> tag.
*
* @example
* // Fetch the learning path data and extract the image
* const response = await fetch( learning path route );
* const data = await response.json();
* const base64String = data.image;
*
* // Use in an <img> element
* <img :src="convertBase64ToImageSrc(base64String)" alt="Learning Path Image" />
*/
export function convertBase64ToImageSrc(base64String: string): string {
return base64String.startsWith("data:image") ? base64String : `data:image/png;base64,${base64String}`;
}

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

View file

@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<main></main>
</template>
<style scoped>
</style>
<style scoped></style>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
import { describe, it, expect, beforeAll } from "vitest";
import { convertBase64ToImageSrc } from "../../src/utils/base64ToImage.js";
import fs from "fs";
import path from "path";
let sampleBase64: string;
beforeAll(() => {
// Load base64 sample from text file
const filePath = path.resolve(__dirname, "base64Sample.txt");
sampleBase64 = fs.readFileSync(filePath, "utf8").trim();
});
describe("convertBase64ToImageSrc", () => {
it("should return the same string if it is already a valid data URL", () => {
const base64Image = `data:image/png;base64,${sampleBase64}`;
expect(convertBase64ToImageSrc(base64Image)).toBe(base64Image);
});
it("should correctly format a raw Base64 string as a PNG image URL", () => {
expect(convertBase64ToImageSrc(sampleBase64)).toBe(`data:image/png;base64,${sampleBase64}`);
});
});

View file

@ -1,14 +1,15 @@
import { fileURLToPath, URL } from 'node:url';
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueDevTools from "vite-plugin-vue-devtools";
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

View file

@ -1,14 +1,14 @@
import { fileURLToPath } from 'node:url';
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config';
import viteConfig from './vite.config';
import { fileURLToPath } from "node:url";
import { mergeConfig, defineConfig, configDefaults } from "vitest/config";
import viteConfig from "./vite.config";
export default mergeConfig(
viteConfig,
defineConfig({
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
root: fileURLToPath(new URL('./', import.meta.url)),
environment: "jsdom",
exclude: [...configDefaults.exclude, "e2e/**"],
root: fileURLToPath(new URL("./", import.meta.url)),
},
}),
);