From d3367f0046312233bea9109487ba4b5b11e92cdf Mon Sep 17 00:00:00 2001 From: lo Date: Mon, 13 Apr 2026 23:17:07 +0200 Subject: [PATCH] Initial commit: FL-Akademie LMS mit Docker, Admin, Portal und Dokumentation. Made-with: Cursor --- .dockerignore | 6 + .env.example | 3 + .gitignore | 6 + Dockerfile | 17 + README.md | 100 ++++ app/admin/courses/[id]/edit/page.tsx | 148 +++++ app/admin/courses/actions.ts | 153 ++++++ app/admin/courses/new/page.tsx | 54 ++ app/admin/courses/page.tsx | 64 +++ app/admin/landing/actions.ts | 51 ++ app/admin/landing/page.tsx | 75 +++ app/admin/layout.tsx | 25 + app/admin/page.tsx | 24 + app/admin/users/page.tsx | 50 ++ app/api/auth/[...nextauth]/route.ts | 6 + app/api/enroll/route.ts | 38 ++ app/api/portal/password/route.ts | 34 ++ app/api/portal/restart/route.ts | 25 + app/api/progress/route.ts | 57 ++ app/dashboard/page.tsx | 5 + app/globals.css | 505 ++++++++++++++++++ .../[slug]/lektionen/[lessonSlug]/page.tsx | 81 +++ app/kurse/[slug]/page.tsx | 90 ++++ app/kurse/page.tsx | 35 ++ app/layout.tsx | 24 + app/login/page.tsx | 16 + app/page.tsx | 68 +++ app/portal/account/page.tsx | 18 + app/portal/certificates/page.tsx | 54 ++ app/portal/layout.tsx | 25 + app/portal/page.tsx | 93 ++++ app/zertifikat/[code]/page.tsx | 46 ++ components/complete-lesson-button.tsx | 38 ++ components/course-card.tsx | 66 +++ components/enroll-button.tsx | 36 ++ components/login-form.tsx | 64 +++ components/password-change-form.tsx | 63 +++ components/print-button.tsx | 9 + components/providers.tsx | 8 + components/restart-course-button.tsx | 34 ++ components/site-footer.tsx | 18 + components/site-header.tsx | 39 ++ docker-compose.yml | 32 ++ docs/HANDBUCH.md | 108 ++++ docs/PLAN.md | 125 +++++ lib/auth-options.ts | 54 ++ lib/certificates.ts | 50 ++ lib/course-progress.ts | 28 + lib/course-queries.ts | 68 +++ lib/format.ts | 21 + lib/landing.ts | 95 ++++ lib/prisma.ts | 11 + lib/session-helpers.ts | 15 + lib/slug.ts | 12 + middleware.ts | 47 ++ next-env.d.ts | 2 + next.config.ts | 7 + package.json | 36 ++ .../20250413120000_init/migration.sql | 141 +++++ .../migration.sql | 27 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 135 +++++ prisma/seed.ts | 203 +++++++ scripts/docker-entrypoint.sh | 8 + tsconfig.json | 21 + types/next-auth.d.ts | 21 + 66 files changed, 3641 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 app/admin/courses/[id]/edit/page.tsx create mode 100644 app/admin/courses/actions.ts create mode 100644 app/admin/courses/new/page.tsx create mode 100644 app/admin/courses/page.tsx create mode 100644 app/admin/landing/actions.ts create mode 100644 app/admin/landing/page.tsx create mode 100644 app/admin/layout.tsx create mode 100644 app/admin/page.tsx create mode 100644 app/admin/users/page.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/api/enroll/route.ts create mode 100644 app/api/portal/password/route.ts create mode 100644 app/api/portal/restart/route.ts create mode 100644 app/api/progress/route.ts create mode 100644 app/dashboard/page.tsx create mode 100644 app/globals.css create mode 100644 app/kurse/[slug]/lektionen/[lessonSlug]/page.tsx create mode 100644 app/kurse/[slug]/page.tsx create mode 100644 app/kurse/page.tsx create mode 100644 app/layout.tsx create mode 100644 app/login/page.tsx create mode 100644 app/page.tsx create mode 100644 app/portal/account/page.tsx create mode 100644 app/portal/certificates/page.tsx create mode 100644 app/portal/layout.tsx create mode 100644 app/portal/page.tsx create mode 100644 app/zertifikat/[code]/page.tsx create mode 100644 components/complete-lesson-button.tsx create mode 100644 components/course-card.tsx create mode 100644 components/enroll-button.tsx create mode 100644 components/login-form.tsx create mode 100644 components/password-change-form.tsx create mode 100644 components/print-button.tsx create mode 100644 components/providers.tsx create mode 100644 components/restart-course-button.tsx create mode 100644 components/site-footer.tsx create mode 100644 components/site-header.tsx create mode 100644 docker-compose.yml create mode 100644 docs/HANDBUCH.md create mode 100644 docs/PLAN.md create mode 100644 lib/auth-options.ts create mode 100644 lib/certificates.ts create mode 100644 lib/course-progress.ts create mode 100644 lib/course-queries.ts create mode 100644 lib/format.ts create mode 100644 lib/landing.ts create mode 100644 lib/prisma.ts create mode 100644 lib/session-helpers.ts create mode 100644 lib/slug.ts create mode 100644 middleware.ts create mode 100644 next-env.d.ts create mode 100644 next.config.ts create mode 100644 package.json create mode 100644 prisma/migrations/20250413120000_init/migration.sql create mode 100644 prisma/migrations/20260209100000_landing_certificates/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts create mode 100644 scripts/docker-entrypoint.sh create mode 100644 tsconfig.json create mode 100644 types/next-auth.d.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..462d3f3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +.next +.git +.env +.env.* +!.env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ea36120 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +DATABASE_URL="postgresql://akademie:devsecret@localhost:5433/akademie" +NEXTAUTH_URL="http://localhost:3000" +NEXTAUTH_SECRET="replace-with-random-32-chars-minimum-in-production" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86e7f2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +.next +.env +.env.local +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b230bf0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:20-bookworm-slim + +WORKDIR /app + +RUN apt-get update -y && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY package.json ./ +RUN npm install --ignore-scripts + +COPY . . +RUN npx prisma generate + +RUN chmod +x scripts/docker-entrypoint.sh + +EXPOSE 3000 + +ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..0bbc116 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# FL-Akademie (Motorrad-Akademie LMS) + +Private Lernplattform als **Ersatz für Tutor LMS** für die [Fahrlässig Motorrad Akademie](https://akademie.fahrlaessig.com/) – Next.js, PostgreSQL, Prisma, NextAuth. Ziel: eigene Kontrolle über Kurse, Nutzer, Landing Page und Zertifikate ohne WordPress-Plugin-Abhängigkeit. + +**Remote-Repository:** `https://git.loepperts.com/loepperts/FL-Akademie.git` + +--- + +## Schnellstart (Docker) + +Voraussetzung: Docker & Docker Compose. + +```bash +git clone https://git.loepperts.com/loepperts/FL-Akademie.git +cd FL-Akademie +docker compose up --build -d +``` + +- **App:** [http://localhost:3000](http://localhost:3000) +- **PostgreSQL (vom Host):** `localhost:5433` → Container-Port 5432 (weniger Konflikte mit lokalem Postgres) + +Beim Start führt der Web-Container aus: `prisma generate`, `prisma migrate deploy`, `prisma db seed`, danach **`next dev`**. + +--- + +## Umgebungsvariablen + +Siehe `.env.example`. In `docker-compose.yml` sind für die Entwicklung bereits Werte gesetzt (`DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`). Für Produktion **eigenes starkes `NEXTAUTH_SECRET`** und korrekte **`NEXTAUTH_URL`** (öffentliche HTTPS-URL) setzen. + +--- + +## Demo-Zugänge (Seed) + +| Rolle | E-Mail | Passwort | +|------------|---------------------------|--------------| +| Admin | `admin@akademie.local` | `devpassword` | +| Dozent | `matze@akademie.local` | `devpassword` | +| Lernender | `lernender@akademie.local`| `devpassword` | + +Der Lernende ist im Demo-Kurs „Modul 1 – Die Fahrschule“ eingeschrieben. + +--- + +## Wichtige Routen + +| Bereich | Pfad | Beschreibung | +|--------|------|----------------| +| Öffentlich | `/` | Startseite (Inhalte aus DB, bearbeitbar im Admin) | +| Kurse | `/kurse`, `/kurse/[slug]` | Katalog, Kurssdetail, Einschreibung (kostenlose Kurse) | +| Lektionen | `/kurse/[slug]/lektionen/[lessonSlug]` | Lernansicht (Login + Einschreibung nötig) | +| Login | `/login` | Anmeldung | +| Mitgliederbereich | `/portal` | Fortschritt, Kurse wiederholen, Link zu Zertifikaten | +| Konto | `/portal/account` | Passwort ändern | +| Zertifikate (Liste) | `/portal/certificates` | Eigene Teilnahmebestätigungen | +| Zertifikat (öffentlich) | `/zertifikat/[code]` | Anzeige/Druck mit Verifikationscode | +| Administration | `/admin` | Nur Rolle `ADMIN`: Kurse, Nutzer, Landing Page | + +`/dashboard` leitet nach `/portal` um. + +--- + +## Dokumentation im Repo + +| Datei | Inhalt | +|--------|--------| +| [docs/PLAN.md](docs/PLAN.md) | Produktvision, Architektur, Phasen, Sicherheit/Monitoring | +| [docs/HANDBUCH.md](docs/HANDBUCH.md) | Betrieb: Admin, Portal, Zertifikate, Datenmodell, Docker-Details | + +--- + +## Projektstruktur (kurz) + +``` +app/ # Next.js App Router (Seiten, API-Routen) +components/ # UI-Komponenten +lib/ # Prisma-Client, Auth, Landing-Parsing, Zertifikate, Fortschritt +prisma/ # schema.prisma, Migrationen, seed.ts +scripts/ # docker-entrypoint.sh +``` + +--- + +## NPM-Skripte (ohne Docker) + +Node 20+, lokales PostgreSQL, `.env` aus `.env.example`: + +```bash +npm install +npx prisma generate +npx prisma migrate dev +npm run dev +``` + +`postinstall` führt kein `prisma generate` aus (Docker/CI-freundlich); nach `npm install` immer **`npx prisma generate`** ausführen. + +--- + +## Lizenz / Nutzung + +Internes Projekt für die Akademie – keine allgemeine Open-Source-Lizenz festgelegt, sofern nicht separat ergänzt. diff --git a/app/admin/courses/[id]/edit/page.tsx b/app/admin/courses/[id]/edit/page.tsx new file mode 100644 index 0000000..72d1b6d --- /dev/null +++ b/app/admin/courses/[id]/edit/page.tsx @@ -0,0 +1,148 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/prisma"; +import { + createLessonAction, + createModuleAction, + updateCourseAction, +} from "@/app/admin/courses/actions"; + +type Props = { params: Promise<{ id: string }> }; + +export default async function AdminEditCoursePage({ params }: Props) { + const { id } = await params; + const course = await prisma.course.findUnique({ + where: { id }, + include: { + modules: { + orderBy: { sortOrder: "asc" }, + include: { lessons: { orderBy: { sortOrder: "asc" } } }, + }, + }, + }); + if (!course) notFound(); + + const priceEuros = (course.priceCents / 100).toFixed(2); + + return ( +
+

+ ← Alle Kurse ·{" "} + Öffentliche Kursseite +

+

Kurs bearbeiten

+

{course.title}

+ +
+

Stammdaten

+ + +