Add course retrieval by share code feature and enhance course search functionality
- Implemented a new endpoint in VocabController to retrieve courses using a share code. - Updated VocabService to include logic for validating share codes and checking course access permissions. - Enhanced course listing functionality with search and language filtering options in the frontend. - Added a dialog for users to input share codes and search for courses, improving user experience. - Updated internationalization files to include new strings for share code functionality and search features.
This commit is contained in:
@@ -26,6 +26,7 @@ class VocabController {
|
||||
this.createCourse = this._wrapWithUser((userId, req) => this.service.createCourse(userId, req.body), { successStatus: 201 });
|
||||
this.getCourses = this._wrapWithUser((userId, req) => this.service.getCourses(userId, req.query));
|
||||
this.getCourse = this._wrapWithUser((userId, req) => this.service.getCourse(userId, req.params.courseId));
|
||||
this.getCourseByShareCode = this._wrapWithUser((userId, req) => this.service.getCourseByShareCode(userId, req.body.shareCode));
|
||||
this.updateCourse = this._wrapWithUser((userId, req) => this.service.updateCourse(userId, req.params.courseId, req.body));
|
||||
this.deleteCourse = this._wrapWithUser((userId, req) => this.service.deleteCourse(userId, req.params.courseId));
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ router.post('/chapters/:chapterId/vocabs', vocabController.addVocabToChapter);
|
||||
router.post('/courses', vocabController.createCourse);
|
||||
router.get('/courses', vocabController.getCourses);
|
||||
router.get('/courses/my', vocabController.getMyCourses);
|
||||
router.post('/courses/find-by-code', vocabController.getCourseByShareCode);
|
||||
router.get('/courses/:courseId', vocabController.getCourse);
|
||||
router.put('/courses/:courseId', vocabController.updateCourse);
|
||||
router.delete('/courses/:courseId', vocabController.deleteCourse);
|
||||
|
||||
@@ -559,27 +559,101 @@ export default class VocabService {
|
||||
return course.get({ plain: true });
|
||||
}
|
||||
|
||||
async getCourses(hashedUserId, { includePublic = true, includeOwn = true } = {}) {
|
||||
async getCourses(hashedUserId, { includePublic = true, includeOwn = true, languageId, search } = {}) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
|
||||
const where = {};
|
||||
const andConditions = [];
|
||||
|
||||
// Zugriffsbedingungen
|
||||
if (includeOwn && includePublic) {
|
||||
where[Op.or] = [
|
||||
{ ownerUserId: user.id },
|
||||
{ isPublic: true }
|
||||
];
|
||||
andConditions.push({
|
||||
[Op.or]: [
|
||||
{ ownerUserId: user.id },
|
||||
{ isPublic: true }
|
||||
]
|
||||
});
|
||||
} else if (includeOwn) {
|
||||
where.ownerUserId = user.id;
|
||||
} else if (includePublic) {
|
||||
where.isPublic = true;
|
||||
}
|
||||
|
||||
// Filter nach Sprache
|
||||
if (languageId) {
|
||||
where.languageId = Number(languageId);
|
||||
}
|
||||
|
||||
// Suche nach Titel oder Beschreibung
|
||||
if (search && search.trim()) {
|
||||
const searchTerm = `%${search.trim()}%`;
|
||||
andConditions.push({
|
||||
[Op.or]: [
|
||||
{ title: { [Op.iLike]: searchTerm } },
|
||||
{ description: { [Op.iLike]: searchTerm } }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// Kombiniere alle AND-Bedingungen
|
||||
if (andConditions.length > 0) {
|
||||
where[Op.and] = andConditions;
|
||||
}
|
||||
|
||||
const courses = await VocabCourse.findAll({
|
||||
where,
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return courses.map(c => c.get({ plain: true }));
|
||||
const coursesData = courses.map(c => c.get({ plain: true }));
|
||||
|
||||
// Lade Sprachnamen für alle Kurse
|
||||
const languageIds = [...new Set(coursesData.map(c => c.languageId))];
|
||||
if (languageIds.length > 0) {
|
||||
const [languages] = await sequelize.query(
|
||||
`SELECT id, name FROM community.vocab_language WHERE id IN (:languageIds)`,
|
||||
{
|
||||
replacements: { languageIds },
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
const languageMap = new Map(languages.map(l => [l.id, l.name]));
|
||||
coursesData.forEach(c => {
|
||||
c.languageName = languageMap.get(c.languageId) || null;
|
||||
});
|
||||
}
|
||||
|
||||
return coursesData;
|
||||
}
|
||||
|
||||
async getCourseByShareCode(hashedUserId, shareCode) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
const code = typeof shareCode === 'string' ? shareCode.trim() : '';
|
||||
|
||||
if (!code || code.length < 6 || code.length > 128) {
|
||||
const err = new Error('Invalid share code');
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
const course = await VocabCourse.findOne({
|
||||
where: { shareCode: code }
|
||||
});
|
||||
|
||||
if (!course) {
|
||||
const err = new Error('Course not found');
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Prüfe Zugriff (öffentlich oder Besitzer)
|
||||
if (course.ownerUserId !== user.id && !course.isPublic) {
|
||||
const err = new Error('Course is not public');
|
||||
err.status = 403;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return course.get({ plain: true });
|
||||
}
|
||||
|
||||
async getCourse(hashedUserId, courseId) {
|
||||
|
||||
Reference in New Issue
Block a user