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:
commit
de0199de96
109 changed files with 3789 additions and 1727 deletions
|
@ -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!");
|
||||
});
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
BIN
frontend/public/assets/home/inclusive.png
Normal file
BIN
frontend/public/assets/home/inclusive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 183 KiB |
BIN
frontend/public/assets/home/innovative.png
Normal file
BIN
frontend/public/assets/home/innovative.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
BIN
frontend/public/assets/home/research_based.png
Normal file
BIN
frontend/public/assets/home/research_based.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
frontend/public/assets/home/socially_relevant.png
Normal file
BIN
frontend/public/assets/home/socially_relevant.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
|
@ -4,9 +4,7 @@
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<router-view/>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(.*)",
|
||||
|
|
18
frontend/src/utils/base64ToImage.ts
Normal file
18
frontend/src/utils/base64ToImage.ts
Normal 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}`;
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<main></main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
1
frontend/tests/base64/base64Sample.txt
Normal file
1
frontend/tests/base64/base64Sample.txt
Normal file
File diff suppressed because one or more lines are too long
23
frontend/tests/base64/base64ToImage.test.ts
Normal file
23
frontend/tests/base64/base64ToImage.test.ts
Normal 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}`);
|
||||
});
|
||||
});
|
|
@ -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)),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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)),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue