Merge branch 'dev' into chore/database-setup

This commit is contained in:
Gabriellvl 2025-02-28 10:32:41 +01:00 committed by GitHub
commit 721be116cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 2396 additions and 452 deletions

View file

@ -2,16 +2,37 @@ import express, { Express, Response } from 'express';
import { initORM } from './orm.js';
import { EnvVars, getNumericEnvVar } from './util/envvars.js';
import themeRoutes from './routes/themes.js';
import studentRouter from './routes/student';
import groupRouter from './routes/group';
import assignmentRouter from './routes/assignment';
import submissionRouter from './routes/submission';
import classRouter from './routes/class';
import questionRouter from './routes/question';
import loginRouter from './routes/login';
const app: Express = express();
const port: string | number = getNumericEnvVar(EnvVars.Port);
// TODO Replace with Express routes
app.get('/', (_, res: Response) => {
res.json({
message: 'Hello Dwengo!',
message: 'Hello Dwengo!🚀',
});
});
app.use('/student', studentRouter);
app.use('/group', groupRouter);
app.use('/assignment', assignmentRouter);
app.use('/submission', submissionRouter);
app.use('/class', classRouter);
app.use('/question', questionRouter);
app.use('/login', loginRouter);
app.use('/theme', themeRoutes);
async function startServer() {
await initORM();

View file

@ -0,0 +1,65 @@
import fs from 'fs';
import path from 'path';
import yaml from 'js-yaml';
import { Request, Response } from 'express';
import { themes } from '../data/themes.js';
interface Translations {
curricula_page: {
[key: string]: { title: string; description?: string }; // Optioneel veld description
};
}
/**
* Laadt de vertalingen uit een YAML-bestand
*/
function loadTranslations(language: string): Translations {
try {
const filePath = path.join(process.cwd(), '_i18n', `${language}.yml`);
const yamlFile = fs.readFileSync(filePath, 'utf8');
return yaml.load(yamlFile) as Translations;
} catch (error) {
console.error(
`Kan vertaling niet laden voor ${language}, fallback naar Nederlands`
);
console.error(error);
const fallbackPath = path.join(process.cwd(), '_i18n', 'nl.yml');
return yaml.load(fs.readFileSync(fallbackPath, 'utf8')) as Translations;
}
}
/**
* GET /themes Haalt de lijst met thema's op inclusief vertalingen
*/
export function getThemes(req: Request, res: Response) {
const language = (req.query.language as string)?.toLowerCase() || 'nl';
const translations = loadTranslations(language);
const themeList = themes.map((theme) => {
return {
key: theme.title,
title:
translations.curricula_page[theme.title]?.title || theme.title,
description: translations.curricula_page[theme.title]?.description,
image: `https://dwengo.org/images/curricula/logo_${theme.title}.png`,
};
});
res.json(themeList);
}
/**
* GET /themes/:theme Geeft de HRUIDs terug voor een specifiek thema
*/
export function getThemeByTitle(req: Request, res: Response) {
const themeKey = req.params.theme;
const theme = themes.find((t) => {
return t.title === themeKey;
});
if (theme) {
res.json(theme.hruids);
} else {
res.status(404).json({ error: 'Thema niet gevonden' });
}
}

196
backend/src/data/themes.ts Normal file
View file

@ -0,0 +1,196 @@
export interface Theme {
title: string;
hruids: string[];
}
export const themes: Theme[] = [
{
title: 'kiks',
hruids: [
'pn_werking',
'un_artificiele_intelligentie',
'pn_klimaatverandering',
'kiks1_microscopie',
'kiks2_practicum',
'pn_digitalebeelden',
'kiks3_dl_basis',
'kiks4_dl_gevorderd',
'kiks5_classificatie',
'kiks6_regressie',
'kiks7_ethiek',
'kiks8_eindtermen',
],
},
{
title: 'art',
hruids: [
'pn_werking',
'un_artificiele_intelligentie',
'art1',
'art2',
'art3',
],
},
{
title: 'socialrobot',
hruids: ['sr0_lkr', 'sr0_lln', 'sr1', 'sr2', 'sr3', 'sr4'],
},
{
title: 'agriculture',
hruids: [
'pn_werking',
'un_artificiele_intelligentie',
'agri_landbouw',
'agri_lopendeband',
],
},
{
title: 'wegostem',
hruids: ['wegostem'],
},
{
title: 'computational_thinking',
hruids: [
'ct1_concepten',
'ct2_concreet',
'ct3_voorbeelden',
'ct6_cases',
'ct9_impact',
'ct10_bebras',
'ct8_eindtermen',
'ct7_historiek',
'ct5_kijkwijzer',
'ct4_evaluatiekader',
],
},
{
title: 'math_with_python',
hruids: [
'pn_werking',
'maths_pythagoras',
'maths_spreidingsdiagrammen',
'maths_rechten',
'maths_lineaireregressie',
'maths_epidemie',
'pn_digitalebeelden',
'maths_logica',
'maths_parameters',
'maths_parabolen',
'pn_regressie',
'maths7_grafen',
'maths8_statistiek',
],
},
{
title: 'python_programming',
hruids: [
'pn_werking',
'pn_datatypes',
'pn_operatoren',
'pn_structuren',
'pn_functies',
'art2',
'stem_insectbooks',
'un_algoenprog',
],
},
{
title: 'stem',
hruids: [
'pn_werking',
'maths_spreidingsdiagrammen',
'pn_digitalebeelden',
'maths_epidemie',
'stem_ipadres',
'pn_klimaatverandering',
'stem_rechten',
'stem_lineaireregressie',
'stem_insectbooks',
],
},
{
title: 'care',
hruids: [
'pn_werking',
'un_artificiele_intelligentie',
'aiz1_zorg',
'aiz2_grafen',
'aiz3_unplugged',
'aiz4_eindtermen',
'aiz5_triage',
],
},
{
title: 'chatbot',
hruids: [
'pn_werking',
'un_artificiele_intelligentie',
'cb5_chatbotunplugged',
'cb1_chatbot',
'cb2_sentimentanalyse',
'cb3_vervoegmachine',
'cb4_eindtermen',
'cb6',
],
},
{
title: 'physical_computing',
hruids: [
'pc_starttodwenguino',
'pc_rijdenderobot',
'pc_theremin',
'pc_leerlijn_introductie',
'pc_leerlijn_invoer_verwerking_uitvoer',
'pc_leerlijn_basisprincipes_digitale_elektronica',
'pc_leerlijn_grafisch_naar_tekstueel',
'pc_leerlijn_basis_programmeren',
'pc_leerlijn_van_µc_naar_plc',
'pc_leerlijn_fiches_dwenguino',
'pc_leerlijn_seriele_monitor',
'pc_leerlijn_bus_protocollen',
'pc_leerlijn_wifi',
'pc_leerlijn_fiches_arduino',
'pc_leerlijn_project_lijnvolger',
'pc_leerlijn_project_bluetooth',
'pc_leerlijn_hddclock',
'pc_leerlijn_fysica_valbeweging',
'pc_leerlijn_luchtkwaliteit',
'pc_leerlijn_weerstation',
'pc_leerlijn_g0',
'pc_leerlijn_g1',
'pc_leerlijn_g3',
'pc_leerlijn_g4',
'pc_leerlijn_g5',
],
},
{
title: 'algorithms',
hruids: [
'art2',
'anm1',
'anm2',
'anm3',
'anm4',
'anm11',
'anm12',
'anm13',
'anm14',
'anm15',
'anm16',
'anm17',
'maths_epidemie',
'stem_insectbooks',
],
},
{
title: 'basics_ai',
hruids: [
'un_artificiele_intelligentie',
'org-dwengo-waisda-taal-murder-mistery',
'art1',
'org-dwengo-waisda-beelden-emoties-herkennen',
'org-dwengo-waisda-beelden-unplugged-fax-lp',
'org-dwengo-waisda-beelden-teachable-machine',
],
},
];

View file

@ -0,0 +1,54 @@
import express from 'express'
const router = express.Router();
// root endpoint used to search objects
router.get('/', (req, res) => {
res.json({
assignments: [
'0',
'1',
]
});
});
// information about an assignment with id 'id'
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
title: 'Dit is een test assignment',
description: 'Een korte beschrijving',
groups: [ '0' ],
learningPath: '0',
class: '0',
links: {
self: `${req.baseUrl}/${req.params.id}`,
submissions: `${req.baseUrl}/${req.params.id}`,
},
});
})
router.get('/:id/submissions', (req, res) => {
res.json({
submissions: [
'0'
],
});
});
router.get('/:id/groups', (req, res) => {
res.json({
groups: [
'0'
],
});
});
router.get('/:id/questions', (req, res) => {
res.json({
questions: [
'0'
],
});
});
export default router

View file

@ -0,0 +1,55 @@
import express from 'express'
const router = express.Router();
// root endpoint used to search objects
router.get('/', (req, res) => {
res.json({
classes: [
'0',
'1',
]
});
});
// information about an class with id 'id'
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
displayName: 'Klas 4B',
teachers: [ '0' ],
students: [ '0' ],
joinRequests: [ '0' ],
links: {
self: `${req.baseUrl}/${req.params.id}`,
classes: `${req.baseUrl}/${req.params.id}/invitations`,
questions: `${req.baseUrl}/${req.params.id}/assignments`,
students: `${req.baseUrl}/${req.params.id}/students`,
}
});
})
router.get('/:id/invitations', (req, res) => {
res.json({
invitations: [
'0'
],
});
})
router.get('/:id/assignments', (req, res) => {
res.json({
assignments: [
'0'
],
});
})
router.get('/:id/students', (req, res) => {
res.json({
students: [
'0'
],
});
})
export default router

View file

@ -0,0 +1,34 @@
import express from 'express'
const router = express.Router();
// root endpoint used to search objects
router.get('/', (req, res) => {
res.json({
groups: [
'0',
'1',
]
});
});
// information about a group (members, ... [TODO DOC])
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
assignment: '0',
students: [ '0' ],
submissions: [ '0' ],
// reference to other endpoint
// should be less hardcoded
questions: `/group/${req.params.id}/question`,
});
})
// the list of questions a group has made
router.get('/:id/question', (req, res) => {
res.json({
questions: [ '0' ],
});
})
export default router

View file

@ -0,0 +1,14 @@
import express from 'express'
const router = express.Router();
// returns login paths for IDP
router.get('/', (req, res) => {
res.json({
// dummy variables, needs to be changed
// with IDP endpoints
leerkracht: '/login-leerkracht',
leerling: '/login-leerling',
});
})
export default router

View file

@ -0,0 +1,38 @@
import express from 'express'
const router = express.Router();
// root endpoint used to search objects
router.get('/', (req, res) => {
res.json({
questions: [
'0',
'1',
]
});
});
// information about an question with id 'id'
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
student: '0',
group: '0',
time: new Date(2025, 1, 1),
content: 'Zijn alle gehele getallen groter dan 2 gelijk aan de som van 2 priemgetallen????',
learningObject: '0',
links: {
self: `${req.baseUrl}/${req.params.id}`,
answers: `${req.baseUrl}/${req.params.id}/answers`,
}
});
})
router.get('/:id/answers', (req, res) => {
res.json({
answers: [
'0'
],
})
})
export default router

View file

@ -0,0 +1,59 @@
import express from 'express'
const router = express.Router();
// root endpoint used to search objects
router.get('/', (req, res) => {
res.json({
students: [
'0',
'1',
]
});
});
// information about a student's profile
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
firstName: 'Jimmy',
lastName: 'Faster',
username: 'JimmyFaster2',
endpoints: {
classes: `/student/${req.params.id}/classes`,
questions: `/student/${req.params.id}/submissions`,
invitations: `/student/${req.params.id}/assignments`,
groups: `/student/${req.params.id}/groups`,
},
});
});
// the list of classes a student is in
router.get('/:id/classes', (req, res) => {
res.json({
classes: [ '0' ],
});
})
// the list of submissions a student has made
router.get('/:id/submissions', (req, res) => {
res.json({
submissions: [ '0' ],
});
})
// the list of assignments a student has
router.get('/:id/assignments', (req, res) => {
res.json({
assignments: [ '0' ],
});
})
// the list of groups a student is in
router.get('/:id/groups', (req, res) => {
res.json({
groups: [ '0' ],
});
})
export default router

View file

@ -0,0 +1,26 @@
import express from 'express'
const router = express.Router();
// root endpoint used to search objects
router.get('/', (req, res) => {
res.json({
submissions: [
'0',
'1',
]
});
});
// information about an submission with id 'id'
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
student: '0',
group: '0',
time: new Date(2025, 1, 1),
content: 'Wortel 2 is rationeel',
learningObject: '0',
});
})
export default router

View file

@ -0,0 +1,58 @@
import express from 'express'
const router = express.Router();
// root endpoint used to search objects
router.get('/', (req, res) => {
res.json({
teachers: [
'0',
'1',
]
});
});
// information about a teacher
router.get('/:id', (req, res) => {
res.json({
id: req.params.id,
firstName: 'John',
lastName: 'Doe',
username: 'JohnDoe1',
links: {
self: `${req.baseUrl}/${req.params.id}`,
classes: `${req.baseUrl}/${req.params.id}/classes`,
questions: `${req.baseUrl}/${req.params.id}/questions`,
invitations: `${req.baseUrl}/${req.params.id}/invitations`,
},
});
})
// the questions students asked a teacher
router.get('/:id/questions', (req, res) => {
res.json({
questions: [
'0'
],
});
});
// invitations to other classes a teacher received
router.get('/:id/invitations', (req, res) => {
res.json({
invitations: [
'0'
],
});
});
// a list with ids of classes a teacher is in
router.get('/:id/classes', (req, res) => {
res.json({
classes: [
'0'
],
});
});
export default router

View file

@ -0,0 +1,9 @@
import express from 'express';
import { getThemes, getThemeByTitle } from '../controllers/themes.js';
const router = express.Router();
router.get('/', getThemes);
router.get('/:theme', getThemeByTitle);
export default router;