Add Vocab Trainer feature with routing, database schema, and translations
- Introduced Vocab Trainer functionality, including new routes for managing languages and chapters. - Implemented database schema for vocab-related tables to ensure data integrity. - Updated navigation and UI components to include Vocab Trainer in the social network menu. - Added translations for Vocab Trainer in both German and English locales, enhancing user accessibility.
This commit is contained in:
@@ -4,6 +4,7 @@ import UserRight from '../models/community/user_right.js';
|
||||
import UserRightType from '../models/type/user_right.js';
|
||||
import UserParamType from '../models/type/user_param.js';
|
||||
import FalukantUser from '../models/falukant/data/user.js';
|
||||
import VocabService from '../services/vocabService.js';
|
||||
|
||||
const menuStructure = {
|
||||
home: {
|
||||
@@ -49,6 +50,11 @@ const menuStructure = {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/gallery"
|
||||
},
|
||||
vocabtrainer: {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/vocab",
|
||||
children: {}
|
||||
},
|
||||
blockedUsers: {
|
||||
visible: ["all"],
|
||||
path: "/socialnetwork/blocked"
|
||||
@@ -296,6 +302,7 @@ const menuStructure = {
|
||||
class NavigationController {
|
||||
constructor() {
|
||||
this.menu = this.menu.bind(this);
|
||||
this.vocabService = new VocabService();
|
||||
}
|
||||
|
||||
calculateAge(birthDate) {
|
||||
@@ -365,6 +372,24 @@ class NavigationController {
|
||||
const age = this.calculateAge(birthDate);
|
||||
const rights = userRights.map(ur => ur.rightType?.title).filter(Boolean);
|
||||
const filteredMenu = await this.filterMenu(menuStructure, rights, age, user.id);
|
||||
|
||||
// Dynamisches Submenü: Treffpunkt → Vokabeltrainer → (Neue Sprache + abonnierte/angelegte)
|
||||
// Wichtig: "Neue Sprache" soll IMMER sichtbar sein – auch wenn die DB-Abfrage (noch) fehlschlägt.
|
||||
if (filteredMenu?.socialnetwork?.children?.vocabtrainer) {
|
||||
const children = {
|
||||
newLanguage: { path: '/socialnetwork/vocab/new' },
|
||||
};
|
||||
try {
|
||||
const langs = await this.vocabService.listLanguagesForMenu(user.id);
|
||||
for (const l of langs) {
|
||||
children[`lang_${l.id}`] = { path: `/socialnetwork/vocab/${l.id}`, label: l.name };
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[menu] Konnte Vokabeltrainer-Sprachen nicht laden:', e?.message || e);
|
||||
}
|
||||
filteredMenu.socialnetwork.children.vocabtrainer.children = children;
|
||||
}
|
||||
|
||||
res.status(200).json(filteredMenu);
|
||||
} catch (error) {
|
||||
console.error('Error fetching menu:', error);
|
||||
|
||||
45
backend/controllers/vocabController.js
Normal file
45
backend/controllers/vocabController.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import VocabService from '../services/vocabService.js';
|
||||
|
||||
function extractHashedUserId(req) {
|
||||
return req.headers?.userid;
|
||||
}
|
||||
|
||||
class VocabController {
|
||||
constructor() {
|
||||
this.service = new VocabService();
|
||||
|
||||
this.listLanguages = this._wrapWithUser((userId) => this.service.listLanguages(userId));
|
||||
this.createLanguage = this._wrapWithUser((userId, req) => this.service.createLanguage(userId, req.body), { successStatus: 201 });
|
||||
this.subscribe = this._wrapWithUser((userId, req) => this.service.subscribeByShareCode(userId, req.body), { successStatus: 201 });
|
||||
this.getLanguage = this._wrapWithUser((userId, req) => this.service.getLanguage(userId, req.params.languageId));
|
||||
|
||||
this.listChapters = this._wrapWithUser((userId, req) => this.service.listChapters(userId, req.params.languageId));
|
||||
this.createChapter = this._wrapWithUser((userId, req) => this.service.createChapter(userId, req.params.languageId, req.body), { successStatus: 201 });
|
||||
this.listLanguageVocabs = this._wrapWithUser((userId, req) => this.service.listLanguageVocabs(userId, req.params.languageId));
|
||||
|
||||
this.getChapter = this._wrapWithUser((userId, req) => this.service.getChapter(userId, req.params.chapterId));
|
||||
this.listChapterVocabs = this._wrapWithUser((userId, req) => this.service.listChapterVocabs(userId, req.params.chapterId));
|
||||
this.addVocabToChapter = this._wrapWithUser((userId, req) => this.service.addVocabToChapter(userId, req.params.chapterId, req.body), { successStatus: 201 });
|
||||
}
|
||||
|
||||
_wrapWithUser(fn, { successStatus = 200 } = {}) {
|
||||
return async (req, res) => {
|
||||
try {
|
||||
const hashedUserId = extractHashedUserId(req);
|
||||
if (!hashedUserId) {
|
||||
return res.status(400).json({ error: 'Missing user identifier' });
|
||||
}
|
||||
const result = await fn(hashedUserId, req, res);
|
||||
res.status(successStatus).json(result);
|
||||
} catch (error) {
|
||||
console.error('Controller error:', error);
|
||||
const status = error.status && typeof error.status === 'number' ? error.status : 500;
|
||||
res.status(status).json({ error: error.message || 'Internal error' });
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default VocabController;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user