Merge branch 'dev' into chore/database-setup
This commit is contained in:
commit
721be116cd
54 changed files with 2396 additions and 452 deletions
|
@ -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();
|
||||
|
||||
|
|
65
backend/src/controllers/themes.ts
Normal file
65
backend/src/controllers/themes.ts
Normal 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
196
backend/src/data/themes.ts
Normal 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',
|
||||
],
|
||||
},
|
||||
];
|
54
backend/src/routes/assignment.ts
Normal file
54
backend/src/routes/assignment.ts
Normal 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
|
55
backend/src/routes/class.ts
Normal file
55
backend/src/routes/class.ts
Normal 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
|
34
backend/src/routes/group.ts
Normal file
34
backend/src/routes/group.ts
Normal 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
|
14
backend/src/routes/login.ts
Normal file
14
backend/src/routes/login.ts
Normal 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
|
38
backend/src/routes/question.ts
Normal file
38
backend/src/routes/question.ts
Normal 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
|
59
backend/src/routes/student.ts
Normal file
59
backend/src/routes/student.ts
Normal 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
|
26
backend/src/routes/submission.ts
Normal file
26
backend/src/routes/submission.ts
Normal 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
|
58
backend/src/routes/teacher.ts
Normal file
58
backend/src/routes/teacher.ts
Normal 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
|
9
backend/src/routes/themes.ts
Normal file
9
backend/src/routes/themes.ts
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue