ich überlege ja, eine mehrsprachige lernplattform zu machen. da es dabei nicht nur um texte geht, möchte ich die inhalte in einer datenbank haben. in angular würde ich normalen lerninhalt dann anzeigen, nachdem ich ihn von der datenbank bekomme. außerdem möchte ich die inhalte zweisprachig anbieten, d.h. bedienelemente hole ich aus i18, die texte der lessons muss ich jedoch je sprache aus der datenbank holen. in einem admin modus möchte ich die inhalte anlegen und pflegen. ich würde beim anlegen gerne den text in bausteinprinzip anlegen. d.h. ich ziehe mir aus einem pool von vordefinierten bausteinen zb ein überschriften-element in eine große liste. dort gebe ich den titel ein. dann ein paragraf-element und ich gebe den text ein. gespeichert werden sollen keine tags, sondern als json liste, wobei jedes element der liste mit einem attribut namens typ beginnt, in dem zb header-h1, paragraf oder image steht. je nach typ kann ich so unterschiedliche attribute dazufügen. wird das html angezeeigt, wird aus dieser json-liste in angular entsprechender code generiert. ein user abonniert einen kurs, fortschritt wird in dashboard angezeigt. pro lesson soll ein fortschritt gespeichert werden. das bedeutet, ein user startet nicht lesson selbst, lesson ist quasi nur das template samt inhalt. unter lessons vom typ test multiple choise tests vorsehe. im lesson json stehen dann die fragen, antworten und welche richtig ist. ans frontend bei der prüfung geht json ohne der angabe, welche richtig ist. der post request gleicht die beiden dann ab und sendet ergebnis wenn man die seite ladet und noch nicht eingeloggt ist, soll man eine übersicht der angebotenen bereiche und kurse sehen. mit einem klick in den kurs kann man auch durch die seiten (nur THEORY) blättern, also ohne lernfortschritt lernen. datenbank-design: user - id, username, fullname, password, role [USER, ADMIN] session - id, userid, accesstoken, accesstoken-expiry category - id, slug, title-de, title-en, orderno course - id, categoryId, slug, title-de, title-en, orderno lesson - id, publicid, courseid, type [THEORY, PRAXIS, TEST], orderno lessoncontent - id, lessonid, language, title, json-text, search-text lessonprogress - id, user_id, courseid, lessonid, status -- wird erst beim inhalt laden angelegt... test_attempt - id, user_id, lesson_id, attempt_no, submitted_at, score_percent, is_passed, given_answers_json restapi-design: POST api/user - registrieren POST api/session - log in DELETE api/session - log out GET api/categories?lang=de - auslesen der bereiche POST api/categories - anlage neuen bereich PATCH api/categories - bearbeiten bereich GET api/courses?lang=de - auslesen der kurs POST api/courses - anlage neuen kurs PATCH api/courses - bearbeiten kurs GET api/courses/{courseSlug}/lessons - Lessons zu einem Kurs GET api/lessons/{publicId}?lang=de - Inhalt zu einem Kurs POST api/lessons/{id}/content/{lang} - Anlage Kurs PATCH api/lessons/{id}/content/{lang} - Anlage Kurs ich vergebe die orderno in 10er schritten, so wäre ein einfaches umsortieren möglich. also zb lesson 10, 20, 30. wenn 30 vor 20 verschoben wird, bekommt er 15. ist nichts mehr frei, kommt am frontend ein fehler, als admin muss man zuvor neusortieren. dafür würde ich einen button machen. am backend würde ich die anzahl selektieren, ergebnis mal 10 rechnen und von hinten weg updaten, sofern erforderlich. In Angular kannst du das mit einem CanDeactivate Guard lösen. Wenn der User die Seite verlassen will, prüft Angular: "Unterscheidet sich das aktuelle JSON im Editor vom JSON, das wir vom Server geladen haben?" Wenn ja, erscheint eine Warnung: "Möchtest du wirklich gehen? Ungespeicherte Änderungen gehen verloren." ================================================================================================================================= In Angular übernehmen diese Aufgabe die sogenannten Route Guards. Seit den neueren Versionen (Angular 15+) werden diese meistens als Functional Guards umgesetzt. Sie fungieren als "Türsteher" für deine Routen: Bevor eine Komponente geladen wird, prüft der Guard, ob der User die nötigen Rechte hat. 1. Die drei wichtigsten Akteure Um das sauber abzubilden, brauchst du ein Zusammenspiel aus drei Teilen: AuthService: Hält den aktuellen Status (Eingeloggt? Welche Rolle?). Guards: Die Logik, die den Zugriff erlaubt oder verweigert. App-Routing-Module: Hier definierst du, welcher Guard für welche Route zuständig ist. // admin.guard.ts export const adminGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router); if (authService.isLoggedIn() && authService.getUserRole() === 'ADMIN') { return true; // Zugriff erlaubt } else { // Nicht berechtigt -> schicke ihn zum Login oder Dashboard return router.parseUrl('/login'); } }; export const routes: Routes = [ { path: 'admin/categories', component: CategoryAdminComponent, canActivate: [adminGuard] // Nur Admins }, { path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] // Jeder eingeloggte User }, { path: 'courses/:slug', component: CourseDetailComponent // Öffentlich (für Gäste) } ]; ================================================================================================================================= Gerne! Das ist ein schöner Anwendungsfall für **Window-Functions** oder einfache Subqueries. Da du die Navigation davon abhängig machst, ob ein User eingeloggt ist oder nicht, nutzen wir eine Logik, die sich flexibel anpasst. Hier ist das SQL-Konzept, das du in deinem Backend (z.B. in einer Node.js/Express-Route) verwenden kannst. ### Die SQL-Abfrage für die "Next/Prev" Logik Wir gehen davon aus, dass du die `id` und den `courseid` der aktuellen Lektion bereits kennst. #### 1. Für einen Gast (Nur THEORY) Hier suchen wir die nächste Lektion im gleichen Kurs, die den Typ `THEORY` hat: ```sql -- Nächste THEORY Lektion finden SELECT publicid FROM lesson WHERE courseid = ? AND orderno > ? AND type = 'THEORY' ORDER BY orderno ASC LIMIT 1; -- Vorherige THEORY Lektion finden SELECT publicid FROM lesson WHERE courseid = ? AND orderno < ? AND type = 'THEORY' ORDER BY orderno DESC LIMIT 1; ``` #### 2. Für einen eingeloggten User (Alle Typen) Hier ist es einfacher, da wir einfach die nächste Lektion nehmen, egal welcher Typ: ```sql -- Nächste Lektion (egal welcher Typ) SELECT publicid FROM lesson WHERE courseid = ? AND orderno > ? ORDER BY orderno ASC LIMIT 1; ``` --- ### Integration in deinen Backend-Code (Pseudo-Code) Du kannst das in einer einzigen Funktion bündeln, die prüft, ob ein User-Objekt vorhanden ist: ```javascript async function getNavigation(currentLesson, user) { const isGuest = !user; // Basis-Query let nextQuery = "SELECT publicid FROM lesson WHERE courseid = ? AND orderno > ?"; let prevQuery = "SELECT publicid FROM lesson WHERE courseid = ? AND orderno < ?"; // Wenn Gast, dann filtere nur THEORY if (isGuest) { nextQuery += " AND type = 'THEORY'"; prevQuery += " AND type = 'THEORY'"; } const next = await db.query(nextQuery + " ORDER BY orderno ASC LIMIT 1", [currentLesson.courseid, currentLesson.orderno]); const prev = await db.query(prevQuery + " ORDER BY orderno DESC LIMIT 1", [currentLesson.courseid, currentLesson.orderno]); return { next: next[0]?.publicid || null, prev: prev[0]?.publicid || null }; } ``` --- ### Warum das für deine Angular-App perfekt ist: 1. **Keine Logik im Frontend:** Angular muss nicht wissen, welche Lektion welche `orderno` hat. Es bekommt einfach nur einen String (die `publicid`) und setzt ihn in den Link. 2. **Sicherheit:** Da das Backend entscheidet, was "Next" ist, kann ein Gast niemals versehentlich (oder absichtlich) durch einfaches Klicken zu einem `TEST` gelangen. 3. **Flexibilität:** Wenn du später einen neuen Typ einführst (z.B. `VIDEO`), musst du nur eine Zeile im Backend ändern, und die Navigation funktioniert sofort überall korrekt. ### Ein kleiner Tipp für die Performance Wenn deine Plattform sehr groß wird, kannst du diese Navigation auch direkt in den Haupt-GET-Request der Lesson einbauen (mittels Subqueries), damit du nur **einen** Datenbank-Aufruf hast statt drei. **Soll ich dir zeigen, wie du diese Navigation in Angular mit einem "Key-Listener" verbindest, damit User mit den Pfeiltasten (Links/Rechts) durch die Lektionen blättern können?**