feat(vocab): add language and course dictionary endpoints and UI components
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
- Implemented `getLanguageDictionary` and `getCourseDictionary` methods in the VocabService to retrieve vocabulary entries filtered by search terms. - Updated VocabController and vocabRouter to include new routes for accessing language and course dictionaries. - Enhanced frontend components to navigate to the new dictionary views, including buttons in VocabCourseView and VocabLanguageView. - Added localization entries for the dictionary feature in multiple languages, ensuring a consistent user experience across the platform.
This commit is contained in:
@@ -18,6 +18,9 @@ class VocabController {
|
||||
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.searchVocabs = this._wrapWithUser((userId, req) => this.service.searchVocabs(userId, req.params.languageId, req.query));
|
||||
this.getLanguageDictionary = this._wrapWithUser((userId, req) =>
|
||||
this.service.getLanguageDictionary(userId, req.params.languageId, req.query)
|
||||
);
|
||||
|
||||
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));
|
||||
@@ -31,6 +34,9 @@ class VocabController {
|
||||
this.getCompletedLessonVocabPool = this._wrapWithUser((userId, req) =>
|
||||
this.service.getCompletedLessonVocabPool(userId, req.params.courseId, req.query.untilLessonId)
|
||||
);
|
||||
this.getCourseDictionary = this._wrapWithUser((userId, req) =>
|
||||
this.service.getCourseDictionary(userId, req.params.courseId, req.query)
|
||||
);
|
||||
this.getVocabDistractorPool = this._wrapWithUser((userId, req) =>
|
||||
this.service.getVocabDistractorPool(userId, req.params.courseId, req.query.beforeLessonId)
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ router.get('/languages/:languageId/chapters', vocabController.listChapters);
|
||||
router.post('/languages/:languageId/chapters', vocabController.createChapter);
|
||||
router.get('/languages/:languageId/vocabs', vocabController.listLanguageVocabs);
|
||||
router.get('/languages/:languageId/search', vocabController.searchVocabs);
|
||||
router.get('/languages/:languageId/dictionary', vocabController.getLanguageDictionary);
|
||||
|
||||
router.get('/chapters/:chapterId', vocabController.getChapter);
|
||||
router.get('/chapters/:chapterId/vocabs', vocabController.listChapterVocabs);
|
||||
@@ -32,6 +33,7 @@ router.get('/courses', vocabController.getCourses);
|
||||
router.get('/courses/my', vocabController.getMyCourses);
|
||||
router.post('/courses/find-by-code', vocabController.getCourseByShareCode);
|
||||
router.get('/courses/:courseId/completed-lesson-vocabs', vocabController.getCompletedLessonVocabPool);
|
||||
router.get('/courses/:courseId/dictionary', vocabController.getCourseDictionary);
|
||||
router.get('/courses/:courseId/distractor-pool', vocabController.getVocabDistractorPool);
|
||||
router.get('/courses/:courseId', vocabController.getCourse);
|
||||
router.put('/courses/:courseId', vocabController.updateCourse);
|
||||
|
||||
@@ -1605,6 +1605,64 @@ export default class VocabService {
|
||||
return { languageId: access.id, results: rows };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wörterbuch: alle Vokabeln einer Trainer-Sprache (Kapitel), optional gefiltert.
|
||||
* Ein Suchbegriff durchsucht Lern- und Referenzspalte (Teilstrings, ILIKE).
|
||||
*/
|
||||
async getLanguageDictionary(hashedUserId, languageId, { q } = {}) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
const access = await this._getLanguageAccess(user.id, languageId);
|
||||
const term = typeof q === 'string' ? q.trim() : '';
|
||||
const like = term ? `%${term}%` : null;
|
||||
|
||||
const rows = await sequelize.query(
|
||||
`
|
||||
SELECT
|
||||
cl.id,
|
||||
c.id AS "chapterId",
|
||||
c.title AS "chapterTitle",
|
||||
l1.text AS "learning",
|
||||
l2.text AS "reference"
|
||||
FROM community.vocab_chapter_lexeme cl
|
||||
JOIN community.vocab_chapter c ON c.id = cl.chapter_id
|
||||
JOIN community.vocab_lexeme l1 ON l1.id = cl.learning_lexeme_id
|
||||
JOIN community.vocab_lexeme l2 ON l2.id = cl.reference_lexeme_id
|
||||
WHERE c.language_id = :languageId
|
||||
${like ? 'AND (l1.text ILIKE :like OR l2.text ILIKE :like)' : ''}
|
||||
ORDER BY c.title ASC, l1.text ASC, l2.text ASC
|
||||
LIMIT 20000
|
||||
`,
|
||||
{
|
||||
replacements: like ? { languageId: access.id, like } : { languageId: access.id },
|
||||
type: sequelize.QueryTypes.SELECT,
|
||||
}
|
||||
);
|
||||
|
||||
return { languageId: access.id, results: rows };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wörterbuch: aus abgeschlossenen Kurslektionen extrahierte Paare, optional gefiltert (Teilstring in beiden Spalten).
|
||||
*/
|
||||
async getCourseDictionary(hashedUserId, courseId, { q } = {}) {
|
||||
const pool = await this.getCompletedLessonVocabPool(hashedUserId, courseId);
|
||||
const term = typeof q === 'string' ? q.trim().toLowerCase() : '';
|
||||
let vocabs = pool.vocabs || [];
|
||||
if (term) {
|
||||
vocabs = vocabs.filter((entry) => {
|
||||
const l = String(entry.learning || '').toLowerCase();
|
||||
const r = String(entry.reference || '').toLowerCase();
|
||||
return l.includes(term) || r.includes(term);
|
||||
});
|
||||
}
|
||||
vocabs.sort((a, b) => {
|
||||
const refCmp = String(a.reference || '').localeCompare(String(b.reference || ''), undefined, { sensitivity: 'base' });
|
||||
if (refCmp !== 0) return refCmp;
|
||||
return String(a.learning || '').localeCompare(String(b.learning || ''), undefined, { sensitivity: 'base' });
|
||||
});
|
||||
return { courseId: pool.courseId, results: vocabs };
|
||||
}
|
||||
|
||||
async addVocabToChapter(hashedUserId, chapterId, { learning, reference }) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
const ch = await this._getChapterAccess(user.id, chapterId);
|
||||
|
||||
Reference in New Issue
Block a user