1

Merge remote-tracking branch 'origin/master' into EventServiceTests

# Conflicts:
#	src/test/java/mops/gruppen2/service/EventServiceTest.java
This commit is contained in:
Mahgs
2020-03-16 13:14:10 +01:00
25 changed files with 411 additions and 254 deletions

View File

@ -4,8 +4,7 @@ WORKDIR /home/gradle/src
RUN gradle bootJar --no-daemon RUN gradle bootJar --no-daemon
FROM openjdk:11-jre-slim FROM openjdk:11-jre-slim
EXPOSE 8080
RUN mkdir /app RUN mkdir /app
COPY --from=build /home/gradle/src/build/libs/*.jar /app/gruppen2.jar COPY --from=build /home/gradle/src/build/libs/*.jar /app/gruppen2.jar
ENTRYPOINT ["java"] ENTRYPOINT ["java"]
CMD ["-jar", "/app/gruppen2.jar"] CMD ["-Dspring.profiles.active=docker", "-jar", "/app/gruppen2.jar"]

View File

@ -1,214 +1,65 @@
= Softwareentwicklung im Team: Vorbereitung auf das Projekt = Gruppenbildung
WS19/20
:icons: font
:icon-set: octicon
:source-highlighter: rouge
ifdef::env-github[]
:tip-caption: :bulb:
:note-caption: :information_source:
:important-caption: :heavy_exclamation_mark:
:caution-caption: :fire:
:warning-caption: :warning:
endif::[]
== Gruppenbildung Das System bietet eine zentrale Oberfläche zum erstellen und verwalten von Gruppen. Diese kann von anderen Systemen eingebunden werden, oder von Studierenden direkt aufgerufen werden. Außerdem wird eine API bereitgestellt für die Nutzung in anderen Systemen. Man kann Private und Öffentliche Gruppen erstellen. Private Gruppen kann man nur über einen Beitrittslink beitreten. Öffentliche Gruppen kann man ohne diesen beitreten. Man kann nach Öffentlichen Gruppen über eine Suchfunktion suchen.
Für das Abschlussprojekt bilden Sie bitte eine Gruppe im GitHub Classroom, die === Problem
aus 7 bis 9 Personen besteht und laden eine entsprechende `group.yml` in AUAS
hoch. Im Gegensatz zu den normalen Blättern haben Sie volle
Administrationsrechte und alle Projekte sind öffentlich um den Austausch mit
anderen Gruppen bei Integrationen zwischen Anwendungen einfacher zu gestalten.
IMPORTANT: Bitte entfernen Sie nicht die Organisatoren aus Ihrem Projekt. Die meisten Teilsysteme von MOPS arbeiten mit Gruppierungen von Studenten: Materialien für Lerngruppen/Veranstaltungen, Gruppenportfolios, Gruppenabstimmungen etc. Jedes System müsste dementsprechend eine Gruppenverwaltung implementieren, was zeitaufwändig ist.
NOTE: Sie finden neben dieser README eine === Ziele
link:group.yml[Beispiel-Gruppe-YML-Datei]. Füllen Sie diese aus, checken Sie
sie ein.
IMPORTANT: Erstellen Sie direkt eine Abgabe in AUAS. Diese ist *essentiell* für Die Gruppenverwaltung wird bieten:
die Teilnahme am Praktikum. Diesmal muss die Abgabe eine ZIP-Datei sein. Die
ZIP-Datei soll die `group.yml` und eine _optionale_ Beschreibung eines eigenen
Systems für MOPS beinhalten. Sie können natürlich auch einfach die
vorgeschlagenen Systeme wählen und müssen keine eigenen Systeme entwerfen.
== Systeme * Eine Seite zum erstellen von neuen Gruppen
* Eine Seite zum verwalten von bestehenden Gruppen
* Eine Übersicht über belegte Gruppen
* Detailansicht für Gruppen
* Eine API zum anfragen von Gruppendaten
*Die folgenden Beschreibungen sind keine Spezifikationen, die den Funktionsumfang der Projekte festlegen,* sondern nur ein "Braindump" von den Ideen, die mir zu dem entsprechenden System gekommen sind. Die Beschreibungen sollen Ihnen primär helfen, Systeme auszusuchen, die Sie umsetzen möchten. Wenn Sie einen eigenen Vorschlag für ein System haben, können Sie das auch gerne vorschlagen. Sie müssen dann zusammen mit der `group.yml` Datei einen kurzen Pitch (in etwa wie in diesem Dokument) einreichen, damit wir uns vorstellen können, was Sie machen wollen und den Umfang begutachten. === Details
=== Online Modulhandbuch Über *Gruppen*:
Im Modulhandbuch werden die Beschreibungen für Veranstaltungen angegeben. Die Beschreibungen werden von den Dozierenden angelegt und werden von einer Verantwortlichen geprüft und dann freigeschaltet.
IMPORTANT: Für die automatische Bearbeitung von Klausurzulassungen muss sichergestellt werden, dass Änderungen an den Veranstaltungen nachvollziehbar bleiben. Wenn wir uns zum Beispiel entscheiden, die Veranstaltung "Foobar" in "Barfoo" umzubenennen, aber im Wesentlichen die Veranstaltung dieselbe ist, dann muss in der neuen Veranstaltung die Information erhalten bleiben, dass alle Zulassungen der alten Veranstaltungen in der neuen Veranstaltung gültig bleiben. * Es gibt private und öffentliche Gruppen
** Privat: Beitritt über Einladungslink
** Öffentlich: Beitritt über Liste
* Jede Gruppe kann einer Veranstalung zugeordnet werden (oder ist eine Veranstaltung)
* Gruppenteilnehmer können Administratorrollen innerhalb der Gruppe einnehmen
* Wenn man eine Gruppe erstellt, wird man dieser direkt hinzugefügt und als Admin registriert (unabhängig davon ob es eine öffentliche oder private Gruppe ist)
* Gruppen verschwinden, wenn sie keine Mitglieder mehr haben
Beschreibungen sollten wie im aktuellen Modulhandbuch strukturiert sein, aber auch zusätzliche Informationen darstellen können. Die einzelnen Informationsblöcke sollen separat befüllt werden und in der REST Schnittstelle (siehe unten) auch separat zugreifbar sein. Es soll _Markdown_ als Textformat für Freitexte verwendet werden. Über *Admin* (interne Rolle):
Das Modulhandbuch muss als PDF Dokument exportiert werden können. * Admin's können anderen Gruppenmitgliedern Adminrechte geben
* Admin's können den Beitrittslink für private Guppen erstellen
* Admin's können andere Mitglieder aus der Gruppe entfernen
Für jedes Semester sollen Teile des Handbuchs auswählbar sein, die in dem Semester stattfinden. Über *Mitglied* (interne Rolle):
Eine HTML Variante des Modulhandbuchs soll zusammen mit der Information, welche Veranstaltungen in welchem Semester stattfinden eine Planungsmöglichkeit für Studierende mit einer Volltext-Such/Filterfunktion liefern. * Mitglieder haben keine Rechte in der Gruppe. Sie können nur eigenständig aus der Gruppe austreten.
Das Handbuch soll für das Belegungssystem eine REST Schnittstelle anbieten, über die die Veranstaltungs-Stammdaten abgerufen werden können. Für *Organisatoren* (keycloak Rolle):
=== Belegung * Beliebige Gruppen können erstellt/gelöscht/bearbeitet werden
Für ein Semester werden Veranstaltungen ausgesucht, die mit MOPS verwaltet werden sollen. Eine solche Veranstaltung ist von Studierenden innerhalb einer Frist selbständig belegbar. Außerhalb der Frist, sollen Organisatorinnen Studierende hinzufügen können. * Teilnehmerlisten nnen per .csv importiert werden
* Monitoringfunktionen
Für jede Veranstaltung soll für das entsprechende Semester eine Syllabus Seite erzeugt werden können. Diese Seite besteht aus beliebig vielen Textblöcken, die in Markdown Syntax befüllt werden. Textblöcke aus dem Modulhandbuch sollen eingefügt werden können. Für *Studenten* (keycloak Rolle) :
Die Syllabus-Seite soll schon vor der Belegung für Studierende lesbar sein. Bei der Belegung wird abgefragt, ob die Voraussetzungen erfüllt sind. Dabei wir der entsprechende Textblock den Studierenden angezeigt und die Studierenden müssen bestätigen, dass sie die Anforderungen erfüllen. Die Anforderungen werden nicht vom System geprüft, da die entsprechenden Daten nicht vorgehalten werden. * Übersicht mit allen belegten Gruppen
* Übersicht mit allen öffentlichen Gruppen
* Gruppen können erstellt werden
* Öffentlichen Gruppen kann beigetreten werden
* Gruppen, in welchen der Student Admin ist, können verwaltet werden
Das System soll optional Belegung von Übungsgruppen möglich machen. Die Belegung soll mindestens nach folgenden Verfahren möglich sein: Über die *Schnittstelle*:
* "First come, First serve" mit eigenem Startdatum (d.h. Startdatum unabhängig von der Belegungsfrist) * Alle Gruppen können angefragt werden, zum Datenabgleich
* Prioritätenverfahren mit Frist * Eine Liste von Gruppen eines Studenten kann angefragt werden (evtl.)
* Eine einzelne Gruppe kann per ID angefragt werden (evtl.)
* etc?
Das System soll die Belegungsdaten per REST Schnittstelle anbieten. Neben der Belegungsinformation zur Veranstaltung, werden auch die laufenden Daten zur Übungsgruppenbelegung benötigt (auch die Daten beim Prioritätsverfahren vor der Zuteilung damit ggf. Kapazitäten angepasst werden können) === Verwendung
=== Gruppenbildung Wir werden ein Dockerfile bereitstellen, das heißt ihr könnt die Anwendung einfach starten.
Diese Anwendung ermöglicht es Studierenden, eigenständig Lerngruppen zu organisieren. Die Lerngruppen sollen dazu dienen Fragen zur Vorlesung oder zu Übungsblättern bei Treffen gemeinsam zu klären. Es soll somit das Finden von Lern- und Abgabepartnerinnen vereinfacht werden. Gruppen sind immer mit einer Veranstaltung verknüpft. Die Organisatorinnen der Veranstaltung haben keinen Zugriff auf die interne Kommunikation, aber erhalten aggregierte statistische Daten über die Gruppen (Teilnehmeranzahl, Anzahl der Interaktionen, ...). Unter localhost:8081/swagger-ui.html findet ihr die API-Dokumentation.
Studierende können selbstständig Gruppen erstellen. Jede Gruppe hat einen Titel und eine Beschreibung. Gruppen können öffentlich oder privat sein. Öffentliche Gruppen können gesucht (Textsuche und Katalog) werden. Der Beitritt zu einer öffentlichen Gruppe erfolgt eigenständig. Privaten Gruppen kann nur beigetreten werden, wenn ein Beitrittslink bekannt ist. Gruppen können gemeinsame Übungsaufgaben einreichen, vorausgesetzt die Gruppe passt zu den Vorgaben für Abgaben (Größe der Gruppe und alle Teilnehmerinnen sind auch Teilnehmerinnen der Veranstaltung für die eine Abgabe eingereicht werden soll)
=== Foren
Organisatorinnen sollen für Veranstaltungen beliebig viele Diskussionsforen anlegen können. Für eine Gruppe soll es auch die Möglichkeit geben, ein Forum anzulegen. Ein Forum soll nur für angemeldete Teilnehmerinnen bzw. Gruppenmitglieder lesbar sein. Die Foren sollten sich an den üblichen Implementierungen im Netz orientieren (Bitte nicht unbedingt an der Art, wie Ilias Beiträge organisiert orientieren). Foren sollen entweder anonym sein, oder automatisch die Klarnamen der Teilnehmerinnen verwenden. Es soll die Möglichkeit geben, für Foren einen Moderationsmodus einzuschalten, bei dem alle Beiträge erst durch eine Moderatorin freigeschaltet werden. Teilehmerinnen sollen die Möglichkeit bekommen Benachrichtigungen für ganze Foren oder auch einzelne Diskussionsthreads ein- bzw. auszuschalten. Das System muss eine Volltextsuche bereitstellen (Leseberechtigungen beachten!).
IMPORTANT: Das Forum muss gegen Sicherheitslücken (z.B. Injection, XSS, ...) abgesichert werden!
=== Nachrichtenzentrale
Die Nachrichtenzentrale dient der Kommunikation zwischen Organisatorinnen (oder
auch anderen Systemen) und Studierenden. Informationen aus den anderen
Teilsystemen (z.B. eine neue Aufgabe oder Korrektur ist verfügbar, eine Frist
läuft ab, neue Materialien wie Aufzeichnung, Übungsblatt, Vorlesungsslides,
...) sollen übersichtlich dargestellt werden. Außerdem sollen Studierende
einstellen können, wie sie bei welchen Nachrichten informiert werden wollen
(z.B. per Mail) und ob die Benachrichtigungen einzeln oder gesammelt als Digest
empfangen werden sollen.
Es sollte eine Möglichkeit geben Studierende (einzeln, Gruppe, ganze
Veranstaltung, nach anderen Kriterien(?)...) außerplanmäßig über Änderungen zu
informieren.
=== Einreichung
Das System soll für Veranstaltungen die Abgabe von Übungsblättern übernehmen. Grundsätzlich besteht eine Abgabe aus einer ZIP-Datei pro Aufgabe eines Übungsblattes. Bei der Einrichtung eines Übungsblattes muss die Abgabestruktur (d.h. die Anzahl der Aufgaben), die Gruppengrößen (in den meisten Fällen 1) und die Abgabefrist festgelegt werden.
Es muss eine Konfigurationsmöglichkeit für die maximale Größe einer Abgabe geben, diese wird global festgelegt.
Die abgegebenen Dateien werden in einer Instanz von https://min.io/[MinIO] (eine Amazon S3 kompatibler Filestore) abgelegt und die Meta-Informationen (zugehörige Person/Gruppe, Einreichungsdatum, Versionen bestehend aus Originaldateiname und URL im Filestore) werden in einer Datenbank gespeichert.
Nach Ende der Abgabefrist können Studierende keine Abgaben mehr einreichen. Organisatoren können immer Einreichung anlegen.
Einzelne Einreichungen können nicht gelöscht werden, auch nicht von Organisatoren. Nach einer gewissen Frist muss es aber möglich sein die Einreichungen (z.B. die Informationen in der DB und die Dateien im Filestore) für einzelne Veranstaltungen vollständig vom Produktionsserver zu löschen. Es muss eine Backup Funktion geben, um die Daten vor der Löschung zu sichern (Es lohnt sich hier, über eine geeignete Struktur im Filestore nachzudenken).
Das System muss Schnittstellen bereitstellen, über die das Korrektursystem die notwendigen Informationen erhält um die Abgaben zu verteilen.
Die Benutzeroberfläche für Organisatoren soll die Historie für Einreichungen zugreifbar machen, d.h. nicht nur die letzte Version, sondern auch alle vorher eingereichten Versionen.
Die Einreichung der Abgaben ist eine der kritischsten Komponenten von MOPS. Insbesondere wollen wir Studierenden die Möglichkeit geben nachzuweisen, dass sie eine Einreichung getätigt haben. Das System soll dazu ein kryptographisches Verfahren verwenden. Pro Datei der Einreichung wird ein sicherer kryptographischer Hashcode berechnet. Die Hashcodes werden zusammen mit dem Einreichdatum vom Server kryptographisch signiert und die so generierte Quittung den Studierenden übergeben. Sollte eine Einreichung verloren gehen, können die Studierenden mit den Originaldateien und der Quittung fälschungssicher nachweisen, dass sie die Einreichung getätigt haben.
IMPORTANT: Es werden hier selbstverständlich keine eigene Implementierung von kryptographischen Algorithmen verwendet, sondern erprobte Bibliotheken benutzt.
=== Korrekturverteilung
Das System organisiert die Korrektur der Einreichungen und die Korrekturergebnisse. Es ist nicht die Schnittstelle für Korrektorinnen, sondern dient den Organisatorinnen der Veranstaltung.
Es sollen Visualisierungen (graphisch, tabellarisch, beides) erzeugt werden, die einen Überblick über den Korrekturstand erlauben:
* Wieviele Abgaben haben die einzelnen Korrektorinnen?
* Wieviele Abgaben sind schon korrigiert? (nur online)
* Wie ist der aktuelle Stand der Korrektur über alle Korrektorinnen aggregiert?
Die Informationen über den Korrekturstand müssen von dem System, in dem die Korrekturen vorgenommen werden bezogen werden.
Die Information über den Gesamtstand kann, falls gewünscht, mit den Studierenden geteilt werden, d.h. es muss eine entsprechende Schnittstelle bereitgestellt werden, die von der Übersichtsseite eingebettet werden kann.
==== Online Korrekturen
Bei der Online Korrektur handelt es sich um Korrekturen von elektronische eingereichten Dateien (z.B. Programme, Textdateien, ...). Die zu begutachtenden Einreichungen werden vom Einreichungsserver über eine Schnittstelle bereitgestellt. Das Korrektursystem verteilt die Einreichungen auf die Korrektorinnen entsprechend eines spezifizierten Schlüssels (z.B. faire Verteilung nach Arbeitsstunden, es gibt aber auch noch andere Möglichkeiten, z.B. Verteilung auf Übungsgruppenleiter oder faire Verteilung nach Teilaufgabe).
==== Offline Korrekturen
Bei der Offline Korrektur handelt es sich um Abgaben, die auf Papier getätigt werden. Hier gibt es keine automatische Verteilung, sondern die Korrektorinnen bekommen einen Stapel Abgaben ausgehändigt. Im System wird die Anzahl der Aufgaben pro Blatt festgelegt (Voreinstellung: 1). Im System können, wenn es gewünscht ist, die Anzahlen der Korrekturen pro Korrektorin eingetragen werden um die Visualisierung der Verteilung zu ermöglichen.
=== Korrekturschnittstelle
Das System ist das Interface, über das Korrektorinnen Zugriff auf die Abgaben erhalten. Die Korrekturen für eine Korrektorin kommen über eine Schnittstelle des Korrekturverteilungssystems.
==== Online Korrektur
Korrektorinnen können die zugewiesenen Abgaben kommentieren und bewerten. Wichtig ist hier, dass der Umgang mit dem System möglichst effizient sein soll (nicht jede einzelnen Datei einzeln herunterladen, Korrektur auf dem Eigenen Rechner und Batch Upload der Kommentare). Es könnte auch überlegt werden für jede Korrektorin ein git Repository automatisch anzulegen.
IMPORTANT: Wenn Dateiinhalte im Browser direkt angezeigt werden, muss auf mögliche Sicherheitslücken (Injection, XSS, ...) geachtet werden.
==== Offline Korrektur
Für manuelle Einreichungen benötigen Korrektorinnen eine Schnittstelle, wo sie die Punkte pro Aufgabe eintragen können. Dazu verwenden sie die Nutzerkennung, die die Studierenden auf die abgabe schreiben müssen. Es werden genauso viele Punktefelder angezeigt, wie im Korrekturverteilungssystem festgelegt wurden.
=== Punkteübersicht
Das System soll Organisatorinnen eine schnelle (buchstäblich!!!) Übersicht über die Situation im Übungsbetrieb geben. Dazu müssen die aktuellen Punktstände für Studierende angezeigt werden können (inklusive der Informationen, welche Punkte gesichert sind, d.h. wenn Punkte eingetragen, aber die Korrektur noch nicht abgeschlossen ist, sollen diese unsicheren Punkte unterscheidbar dargestellt werden).
Hier brauchen wir auch Visualisierungen für aggregierte Daten durchschnittliche Punktzahl, Abweichungen, Punkte nach Blättern, Punkte nach Aufgaben etc. Hier sind Darstellungen gefragt, die uns Problem im Übungsbetrieb aufzeigen können gefragt.
=== Terminfindung und Abstimmung
Um einen gemeinsamen Termin mit mehreren Personen abzustimmen, kann man in diesem System ein Eintrag angelegt werden. Ein Eintrag besteht aus einem Titel, einem Ort, einer optionalen Beschreibung und Vorschlägen für Termine. Die Terminvorschläge sollen sowohl über eine einfach zu bedienende graphische Oberfläche (hier könnte doodle.com oder auch terminplaner.dfn.de als Vorbild genommen werden) eingegeben, als auch über ein Textfile importiert werden können. Es soll auch die Option geben über Fragen abzustimmen. Auch Kommentare sollen abgegeben werden können.
Terminfindung und Abstimmung können mit einer Gruppe verknüpft werden. Dann können nur Gruppenmitglieder teilnehmen. Alternativ kann der Zugang per Link erfolgen. Jede Person, die den Link kennt, kann dann abstimmen.
Die Abstimmung kann unter dem Klarnamen oder Pseudonym erfolgen.
Für alle Terminfindungs- und Abstimmungsprozesse soll ein Datum angegeben werden, an dem die den Prozess betreffenden Daten automatisch gelöscht werden.
=== Java in der Praxis: Selfservice
Für Veranstaltungen der rheinjug können Kreditpunkte erworben werden. Für je 0.5 CP werden drei normale Abendveranstaltungen oder eine Entwickelbar Veranstaltung besucht und pro Veranstaltung eine kurze Zusammenfassung geschrieben. Die Veranstaltungstermine können über die API von meetup.com abgerufen werden.
Studierende sollen sich bei dem System für eine kommende Veranstaltung anmelden und nach dem Besuch innerhalb einer Woche die Zusammenfassung einreichen. Die Zusammenfassung wird unter einer CC Lizenz, die Autoren können aussuchen, ob sie namentlich bei einer Veröffentlichung genannt werden wollen oder nicht.
Die Zusammenfassungen werden von einem Verantwortlichen akzeptiert. Nichtakzeptieren (z.B. weil es inhaltliche Mängel gibt) muss vom System nicht behandelt werden, das erfolgt durch den Verantwortlichen direkt per Mail.
Studierende, die hinreichend viele Veranstaltungen besucht haben, können diese gegen einen Schein eintauschen. Das System stellt sicher, dass die Bedingungen für die Vergabe erfüllt sind und erzeugt ein PDF, das durch den Verantwortlichen gedruckt und unterschrieben wird. "Verbrauchte" Vorträge können nicht mehrfach benutzt werden und "unverbrauchte" Vorträge bleiben für einen späteren Zeitpunkt erhalten.
IMPORTANT: Könnte man das vielleicht auch mit kryptographischen Quittungen lösen um die gespeicherten personenbezogenen Daten zu minimieren? Die Texte müssen auf jeden Fall gespeichert werden (inkl. Namen, falls gewünscht) und wir sollten auch Statistische Informationen haben (Wieviele Scheine werden ausgestellt? Wieviele und welche Vorträge werden zusammengefasst? ...). Es ist hier auch daran zu denken dass die Quittungen nur einmal verwendet werden können, d.h., wir müssen auf jeden Fall auch Statusinformationen speichern, die können aber frei von personenbezogenen Daten sein.
=== Korrektorinnen Bewerbung
In jedem Semester werden studentische Hilfskräfte für den Übungsbetrieb benötigt. In (zumindest) den Grundlagenveranstaltungen wird dazu ein gemeinsames Bewerbungsverfahren benutzt:
* Bewerber füllen einen Fragebogen aus.
* Nach Ablauf der Frist werden die Bewerberinnen, die potentiell für eine Stelle in Frage kommen gruppiert und den Verantwortlichen der Veranstaltung zur Verfügung gestellt. Bewerberinnen kommen in Frage, wenn sie eine Veranstaltung nicht ausgeschlossen haben.
* Die Verantwortlichen geben für jede Bewerbung eine Priorität an.
* Die Verteilung auf die einzelnen Veranstaltungen werden von einer verantwortlichen Person manuell durchgeführt, dazu wird aber eine hinreichend gute Darstellung der gesammelten Informationen gebraucht
* Am Ende sollen automatisch die Einstellungsbögen für die Personalabteilung als PDF erzeugt werden
=== Feedback
Das System soll Feedback von Studierenden einsammeln. Als Einheit soll im Folgenden ein einzelner Vorlesungs- oder Übungstermin oder auch eine Aufgabe bezeichnet werden.
Die Feedbackfunktion wird von den Lehrenden für Einheiten aktiviert. Die Aktivierung erfolgt entweder global nach bestimmten Kriterien (z.B. alle Vorlesungen oder alle Aufgaben) oder für einzelne Einheiten. Zu jedem Feedback gibt es einen Zeitraum, in dem das Feedback gesammelt wird.
Das Feedback soll den Lehrenden angemessen angezeigt werden. Für bestimmtes Feedback (z.B. allgemeine Zufriedenheit) soll auch ein zeitlicher Verlauf dargestellt werden.
Feedback kann den Studierenden zur Verfügung gestellt werden. Es kann notwendig sein, bestimmte Stellen vorher zu zensieren (z.B. bei beleidigenden Kommentare gegenüber studentischen Hilfskräften, etc.)
*Besonderheit*: Feedback ist anonym! Es muss hier darauf geachtet werden, dass das Feedback zwar nur von berechtigten Personen kommt (d.h. Studierende müssen auch an der Veranstaltung teilnehmen). Es darf aber nicht nachvollziehbar sein (auch nicht im Logfile), wer ein Feedback abgegeben hat.
=== Lernportfolios/Lerntagebücher/Lernwiki
Ein Lernportfolio ist eine "Mappe", in der Arbeitsprozesse durch Studierende dokumentiert werden. Außerdem können in einem Portfolio auch Arbeitsergebnisse (Texte, Programmcode, Protokolle, ...) gespeichert werden. Das didaktische Ziel ist die Reflexion über die eigenen Lernprozesse und Entwicklung zu fördern. Es sollte durch die Lehrenden möglich sein, eine Strukturierung oder Beispiele vorzugeben. Portfolios sollten einer Veranstaltung zugeordnet sein und es sollte sowohl Einzel- als auch Gruppenportfolios geben.
=== Klausurzulassung
Das System soll für Veranstaltungen die Klausurzulassung verarbeiten und zusammen mit der Anmeldeliste eine Klausurliste erzeugen können.
MOPS erhält folgende Daten:
* Eine Liste von Personen, die die Zulassung im Semester erworben haben. Die Liste wird manuell erstellt oder falls die Zulassungskriterien automatisch geprüft werden können automatisch generiert.
* Die Anmeldeliste für eine Klausur. Diese wird von der zuständigen Lehrkraft im Dozierendenportal heruntergeladen und in MOPS hochgeladen.
* Zusätzlich verwaltet MOPS Altzulassungen, die von den Studierenden bis zu einem festgelegten Stichtag eingereicht werden müssen.
In der Informatik gibt es die Übereinkunft, das Klausurzulassungen bestehen bleiben, wir nennen das eine Altzulassung. Da in den Grundlagenveranstaltungen die Dozierenden wechseln, ist es nicht ganz einfach die Altzulassungen im Blick zu behalten. Ein zentrales System, das die Informationen speichert, ist aus Datenschutzgründen nicht wünschenswert. MOPS soll am Ende des Semesters die Informationen bekommen, welche Studierenden neu zugelassen wurden und für jede dieser Personen eine kryptographisch abgesicherte Quittung erstellen und der Person zukommen lassen.
Eine solche Quittung beinhaltet in maschinen- und menschenlesbarem Klartext die Information in welchem Semester die Zulassung für welche Veranstaltung erreicht wurde. Die Quittung kann von Studierenden verwendet werden, um eine bestehende Altzulassung nachzuweisen. Dazu reicht die Person die Quittung bei dem System fristgerecht die Quittung ein. Die Quittung wird geprüft, ob sie für die Veranstaltung gültig ist und ob die kryptographische Signatur gültig ist.
Wenn die Informationen über die Zulassungen zusammengeführt sind, soll für die Lehrenden eine Zulassungsliste ein einem (mit MS Excel/Libre Office) bearbeitbaren Format generiert werden. Die Datei muss bearbeitet werden können, da in der Regel die Studierenden auf verschiedene Hörsäle verteilt werden und für jeden Saal eine eigene Liste gedruckt wird.
Eine Prüfung einer Quittung muss auch manuell durch eine Organisatorin erfolgen können. Es müssen auch manuell Altzulassungen eingetragen werden können.
IMPORTANT: Es werden hier selbstverständlich keine eigene Implementierung von kryptographischen Algorithmen verwendet, sondern erprobte Bibliotheken benutzt.
=== Materialsammlung
Die Materialsammlung soll Dokumente, die von Organisatorinnen für eine Veranstaltung bereitgestellt werden verwalten. Beispiele für Materialien sind Skripte, Übungsblätter, Vorlesungsslides, Videos, Artikel, Links, usw.
Es wäre gut, wenn die Materialien mit Tags (inhaltlich und organisatorisch) versehen werden, so dass man verschiedene Sichten/Filter auf die Materialien bekommt, z.B. alles zum Thema Git, alle Vorlesungsslides, alles, was als klausurrelevant markiert wurde. Eine Volltextsuche für Standardinhalte (z.B. pdf) oder Metadatensuche (z.B. nach Datum) wäre auch hilfreich. Es sollte auch ein Veröffentlichungsdatum geben, zu dem eine Resource verfügbar ist.
Es sollte https://min.io/[MinIO] verwendet werden, um die Dateien abzulegen.

View File

@ -71,6 +71,7 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2' runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'
testImplementation 'org.assertj:assertj-core:3.15.0' testImplementation 'org.assertj:assertj-core:3.15.0'
testImplementation('org.springframework.boot:spring-boot-starter-test') { testImplementation('org.springframework.boot:spring-boot-starter-test') {

21
docker-compose.yaml Normal file
View File

@ -0,0 +1,21 @@
version: "3.7"
services:
dbmysql:
image: mysql:5.7
container_name: 'dbmysql'
environment:
MYSQL_DATABASE: 'gruppen2'
MYSQL_USER: 'root'
MYSQL_ROOT_PASSWORD: 'geheim'
restart: always
volumes:
- './mysql/db/storage:/var/lib/mysql'
ports:
- '3306:3306'
gruppen2app:
build: .
container_name: 'gruppen2app'
depends_on:
- dbmysql
ports:
- '8081:8080'

View File

@ -2,22 +2,24 @@ package mops.gruppen2.controller;
import mops.gruppen2.config.Gruppen2Config; import mops.gruppen2.config.Gruppen2Config;
import mops.gruppen2.domain.Exceptions.EventException; import mops.gruppen2.domain.Exceptions.EventException;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.GroupType; import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.Role;
import mops.gruppen2.domain.User; import mops.gruppen2.domain.User;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.domain.event.AddUserEvent;
import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.domain.event.UpdateGroupDescriptionEvent;
import mops.gruppen2.domain.event.UpdateGroupTitleEvent;
import mops.gruppen2.security.Account; import mops.gruppen2.security.Account;
import mops.gruppen2.service.*; import mops.gruppen2.service.*;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import javax.annotation.security.RolesAllowed; import javax.annotation.security.RolesAllowed;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Controller @Controller
@RequestMapping("/gruppen2") @RequestMapping("/gruppen2")
@ -55,6 +57,7 @@ public class Gruppen2Controller {
model.addAttribute("account", keyCloakService.createAccountFromPrincipal(token)); model.addAttribute("account", keyCloakService.createAccountFromPrincipal(token));
model.addAttribute("gruppen", userService.getUserGroups(user.getUser_id())); model.addAttribute("gruppen", userService.getUserGroups(user.getUser_id()));
model.addAttribute("user",user);
return "index"; return "index";
} }
@ -67,20 +70,46 @@ public class Gruppen2Controller {
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator)"}) @RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator)"})
@GetMapping("/findGroup") @GetMapping("/findGroup")
public String findGroup(KeycloakAuthenticationToken token, Model model) { public String findGroup(KeycloakAuthenticationToken token, Model model, @RequestParam(value = "suchbegriff", required = false) String suchbegriff) throws EventException {
List<Group> groupse = new ArrayList<>();
if(suchbegriff!=null) {
groupse = groupService.findGroupWith(suchbegriff);
}
model.addAttribute("account", keyCloakService.createAccountFromPrincipal(token)); model.addAttribute("account", keyCloakService.createAccountFromPrincipal(token));
model.addAttribute("gruppen",groupse);
return "search"; return "search";
} }
@PostMapping("/createGroup") @PostMapping("/createGroup")
public String pCreateGroup(KeycloakAuthenticationToken token, public String pCreateGroup(KeycloakAuthenticationToken token,
@RequestParam(value = "title") String title, @RequestParam(value = "title") String title,
@RequestParam(value = "beschreibung") String beschreibung) { @RequestParam(value = "beschreibung") String beschreibung,
@RequestParam(value = "visibility", required = false) Boolean visibility) {
Account account = keyCloakService.createAccountFromPrincipal(token); Account account = keyCloakService.createAccountFromPrincipal(token);
controllerService.createGroup(account, title, beschreibung); if (visibility == null) {
visibility = true;
}else{
visibility = false;
}
controllerService.createGroup(account, title, beschreibung, visibility);
return "redirect:/gruppen2/"; return "redirect:/gruppen2/";
} }
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator)"})
@GetMapping("/details")
public String showGroupDetails(KeycloakAuthenticationToken token, Model model, @RequestParam (value="id") Long id) throws EventException, ResponseStatusException {
model.addAttribute("account", keyCloakService.createAccountFromPrincipal(token));
Group group = userService.getGroupById(id);
Account account = keyCloakService.createAccountFromPrincipal (token);
User user = new User(account.getName(), account.getGivenname(), account.getFamilyname(), account.getEmail());
if(group!= null) {
model.addAttribute("group", group);
model.addAttribute("role", group.getRoles().get(user.getUser_id()));
return "detailsMember";
}
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Group not found");
}
} }

View File

@ -3,6 +3,8 @@ package mops.gruppen2.controller;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletRequest;
@Controller @Controller
public class MopsController { public class MopsController {
@ -10,4 +12,10 @@ public class MopsController {
public String redirect(){ public String redirect(){
return "redirect:/gruppen2/"; return "redirect:/gruppen2/";
} }
@GetMapping("/logout")
public String logout(HttpServletRequest request) throws Exception {
request.logout();
return "redirect:/gruppen2/";
}
} }

View File

@ -12,4 +12,5 @@ public class EventDTO {
Long group_id; Long group_id;
String user_id; String user_id;
String event_payload; String event_payload;
boolean visibility;
} }

View File

@ -30,6 +30,7 @@ public class Event {
Long group_id; Long group_id;
String user_id; String user_id;
public Event(Long group_id,String user_id){ public Event(Long group_id,String user_id){
this.group_id = group_id; this.group_id = group_id;
this.user_id = user_id; this.user_id = user_id;

View File

@ -22,4 +22,5 @@ public class UpdateRoleEvent extends Event {
super(group_id, user_id); super(group_id, user_id);
this.newRole = newRole; this.newRole = newRole;
} }
} }

View File

@ -16,6 +16,12 @@ public interface EventRepository extends CrudRepository<EventDTO, Long> {
@Query("select * from event where group_id =:id") @Query("select * from event where group_id =:id")
List<EventDTO> findEventDTOByGroup_id(@Param("id") Long group_id); List<EventDTO> findEventDTOByGroup_id(@Param("id") Long group_id);
//@Query("SELECT * FROM event WHERE event_id > ?#{[0]}")
//Iterable<EventDTO> findNewEventSinceStatus(@Param("status") Long status);
@Query("select * from event where visibility =:vis")
List<EventDTO> findEventDTOByVisibility(@Param("vis") Boolean visibility);
@Query("SELECT DISTINCT group_id FROM event WHERE event_id > :status") @Query("SELECT DISTINCT group_id FROM event WHERE event_id > :status")
public List<Long> findNewEventSinceStatus(@Param("status") Long status); public List<Long> findNewEventSinceStatus(@Param("status") Long status);

View File

@ -1,5 +1,6 @@
package mops.gruppen2.service; package mops.gruppen2.service;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.GroupType; import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.Role; import mops.gruppen2.domain.Role;
import mops.gruppen2.domain.Visibility; import mops.gruppen2.domain.Visibility;
@ -26,15 +27,26 @@ public class ControllerService {
* @param title Gruppentitel * @param title Gruppentitel
* @param description Gruppenbeschreibung * @param description Gruppenbeschreibung
*/ */
public void createGroup(Account account, String title, String description) { public void createGroup(Account account, String title, String description, Boolean visibility) {
Visibility visibility1;
if (visibility){
visibility1 = Visibility.PUBLIC;
}else{
visibility1 = Visibility.PRIVATE;
}
List<Event> eventList = new ArrayList<>(); List<Event> eventList = new ArrayList<>();
Collections.addAll(eventList, new CreateGroupEvent(eventService.checkGroup(), account.getName(), null , GroupType.LECTURE, Visibility.PUBLIC), Collections.addAll(eventList, new CreateGroupEvent(eventService.checkGroup(), account.getName(), null , GroupType.LECTURE, visibility1),
new AddUserEvent(eventService.checkGroup(), account.getName(),account.getGivenname(),account.getFamilyname(),account.getEmail()), new AddUserEvent(eventService.checkGroup(), account.getName(),account.getGivenname(),account.getFamilyname(),account.getEmail()),
new UpdateRoleEvent(eventService.checkGroup(), account.getName(), Role.ADMIN), new UpdateRoleEvent(eventService.checkGroup(), account.getName(), Role.ADMIN),
new UpdateGroupTitleEvent(eventService.checkGroup(), account.getName(), title), new UpdateGroupTitleEvent(eventService.checkGroup(), account.getName(), title),
new UpdateGroupDescriptionEvent(eventService.checkGroup(), account.getName(), description)); new UpdateGroupDescriptionEvent(eventService.checkGroup(), account.getName(), description),
new UpdateRoleEvent(eventService.checkGroup(),account.getName(), Role.ADMIN));
eventService.saveEventList(eventList); eventService.saveEventList(eventList);
} }
public void addUser(Account account, Group group){
AddUserEvent addUserEvent = new AddUserEvent(eventService.checkGroup(),group.getId(),account.getName(),account.getGivenname(),account.getFamilyname(),account.getEmail());
eventService.saveEvent(addUserEvent);
}
} }

View File

@ -2,9 +2,12 @@ package mops.gruppen2.service;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import mops.gruppen2.domain.EventDTO; import mops.gruppen2.domain.EventDTO;
import mops.gruppen2.domain.Exceptions.EventException;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.event.Event;
import mops.gruppen2.repository.EventRepository; import mops.gruppen2.repository.EventRepository;
import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
@ -29,7 +32,8 @@ public class EventService {
eventStore.save(eventDTO); eventStore.save(eventDTO);
} }
/** Erzeugt aus einem Event Objekt ein EventDTO Objekt /** Erzeugt aus einem Event Objekt ein EventDTO Objekt.
* Ist die Gruppe öffentlich, dann wird die visibility auf true gesetzt.
* *
* @param event * @param event
* @return EventDTO * @return EventDTO
@ -38,6 +42,15 @@ public class EventService {
EventDTO eventDTO = new EventDTO(); EventDTO eventDTO = new EventDTO();
eventDTO.setGroup_id(event.getGroup_id()); eventDTO.setGroup_id(event.getGroup_id());
eventDTO.setUser_id(event.getUser_id()); eventDTO.setUser_id(event.getUser_id());
if(event instanceof CreateGroupEvent) {
if(((CreateGroupEvent) event).getGroupVisibility() == Visibility.PRIVATE) {
eventDTO.setVisibility(false);
}else {
eventDTO.setVisibility(true);
}
}
try { try {
eventDTO.setEvent_payload(serializationService.serializeEvent(event)); eventDTO.setEvent_payload(serializationService.serializeEvent(event));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
@ -76,7 +89,7 @@ public class EventService {
return translateEventDTOs(groupEventDTOS); return translateEventDTOs(groupEventDTOS);
} }
/** Erzeugt aus der Datenbank eine Liste von Events /** Erzeugt aus einer Liste von eventDTOs eine Liste von Events
* *
* @param eventDTOS * @param eventDTOS
* @return Liste von Events * @return Liste von Events
@ -87,7 +100,6 @@ public class EventService {
for (EventDTO eventDTO : eventDTOS) { for (EventDTO eventDTO : eventDTOS) {
try { try {
events.add(serializationService.deserializeEvent(eventDTO.getEvent_payload())); events.add(serializationService.deserializeEvent(eventDTO.getEvent_payload()));
}catch (JsonProcessingException e) { }catch (JsonProcessingException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@ -23,6 +23,12 @@ public class GroupService {
this.eventRepository = eventRepository; this.eventRepository = eventRepository;
} }
/** Sucht in der DB alle Zeilen raus welche eine der Gruppen_ids hat.
* Wandelt die Zeilen in Events um und gibt davon eine Liste zurück.
*
* @param group_ids
* @return
*/
public List<Event> getGroupEvents(List<Long> group_ids) { public List<Event> getGroupEvents(List<Long> group_ids) {
List<EventDTO> eventDTOS = new ArrayList<>(); List<EventDTO> eventDTOS = new ArrayList<>();
List<Event> events = new ArrayList<>(); List<Event> events = new ArrayList<>();
@ -32,8 +38,13 @@ public class GroupService {
return events = eventService.translateEventDTOs(eventDTOS); return events = eventService.translateEventDTOs(eventDTOS);
} }
/** Erzeugt eine neue Map wo Gruppen aus den Events erzeugt und den Gruppen_ids zugeordnet werden.
* Die Gruppen werden als Liste zurückgegeben
*
* @param events
* @return
* @throws EventException
*/
public List<Group> projectEventList(List<Event> events) throws EventException { public List<Group> projectEventList(List<Event> events) throws EventException {
Map<Long, Group> groupMap = new HashMap<>(); Map<Long, Group> groupMap = new HashMap<>();
@ -44,7 +55,13 @@ public class GroupService {
return new ArrayList<>(groupMap.values()); return new ArrayList<>(groupMap.values());
} }
// /** guckt in der Map anhand der Id nach ob die Gruppe schon in der Map vorhanden ist, wenn nicht wird eine neue
* Gruppe erzeugt
*
* @param groups
* @param group_id
* @return
*/
private Group getOrCreateGroup(Map<Long, Group> groups, long group_id) { private Group getOrCreateGroup(Map<Long, Group> groups, long group_id) {
if (!groups.containsKey(group_id)) { if (!groups.containsKey(group_id)) {
groups.put(group_id, new Group()); groups.put(group_id, new Group());
@ -52,4 +69,33 @@ public class GroupService {
return groups.get(group_id); return groups.get(group_id);
} }
/**
* sucht alle Zeilen in der DB wo die Visibility gleich true ist und wandelt diese in
* eine Liste von Gruppen
* @return
* @throws EventException
*/
public List<Group> getAllGroupWithVisibilityPublic() throws EventException {
return projectEventList(eventService.translateEventDTOs(eventRepository.findEventDTOByVisibility(Boolean.TRUE)));
}
/**
* Filtert alle öffentliche Gruppen nach dem suchbegriff und gibt diese als Liste von Gruppen zurück.
* Groß und kleinschreibung wird beachtet.
* @param search
* @return
* @throws EventException
*/
public List<Group> findGroupWith(String search) throws EventException {
List<Group> groups = new ArrayList<>();
for (Group group: getAllGroupWithVisibilityPublic()) {
if (group.getTitle().contains(search)){
groups.add(group);
}
}
return groups;
}
} }

View File

@ -29,6 +29,7 @@ public class SerializationService {
* @return JSON-Event-Payload als String * @return JSON-Event-Payload als String
* @throws JsonProcessingException * @throws JsonProcessingException
*/ */
public String serializeEvent(Event event) throws JsonProcessingException { public String serializeEvent(Event event) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(event); return mapper.writeValueAsString(event);

View File

@ -6,6 +6,7 @@ import mops.gruppen2.domain.event.Event;
import mops.gruppen2.repository.EventRepository; import mops.gruppen2.repository.EventRepository;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Service @Service
@ -26,4 +27,11 @@ public class UserService {
List<Event> events = groupService.getGroupEvents(group_ids); List<Event> events = groupService.getGroupEvents(group_ids);
return groupService.projectEventList(events); return groupService.projectEventList(events);
} }
public Group getGroupById(Long group_id) throws EventException {
List<Long> group_ids = new ArrayList<>();
group_ids.add(group_id);
List<Event> events = groupService.getGroupEvents(group_ids);
return groupService.projectEventList(events).get(0);
}
} }

View File

@ -0,0 +1,14 @@
application.name=gruppen2
logging.pattern.console=[${application.name}],%magenta(%-5level), %d{dd-MM-yyyy HH:mm:ss.SSS}, %highlight(%msg),%thread,%logger.%M%n
spring.datasource.initialization-mode=always
spring.datasource.url=jdbc:mysql://dbmysql:3306/gruppen2
spring.datasource.username=root
spring.datasource.password=geheim
keycloak.principal-attribute=preferred_username
keycloak.auth-server-url=https://keycloak.cs.hhu.de/auth
keycloak.realm=MOPS
keycloak.resource=demo
keycloak.public-client=true

View File

@ -7,5 +7,6 @@ CREATE TABLE event
event_id INT PRIMARY KEY AUTO_INCREMENT, event_id INT PRIMARY KEY AUTO_INCREMENT,
group_id INT NOT NULL, group_id INT NOT NULL,
user_id VARCHAR(50), user_id VARCHAR(50),
event_payload VARCHAR(255) event_payload VARCHAR(255),
visibility BOOLEAN
); );

View File

@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" <html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}"> th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}"
xmlns="http://www.w3.org/1999/html">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Name des Subsystems</title> <title>Gruppenerstellung</title>
<th:block th:fragment="headcontent"> <th:block th:fragment="headcontent">
<!-- Links, Skripts, Styles hier einfügen! --> <!-- Links, Skripts, Styles hier einfügen! -->
</th:block> </th:block>
@ -28,16 +29,30 @@
<h1>Gruppenerstellung</h1> <h1>Gruppenerstellung</h1>
<div class="container-fluid"> <div class="container-fluid">
<form method="post" action="/gruppen2/createGroup"> <form method="post" action="/gruppen2/createGroup">
<div style="border: 10px solid aliceblue; background: aliceblue"> <div class="shadow p-2" style=" border: 10px solid aliceblue; background: aliceblue">
<div class="form-group"> <div class="form-group">
<label for="titel">Name der Gruppe</label> <label for="titel">Titel</label>
<input type="text" class="form-control" id="titel" th:name="title"> <input type="text" class="form-control" id="titel" th:name="title" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="beschreibung">Beschreibung der Gruppe</label> <label for="beschreibung">Beschreibung</label>
<textarea th:name="beschreibung" class="form-control" id="beschreibung" rows="3"></textarea> <textarea th:name="beschreibung" class="form-control" id="beschreibung" rows="3" required></textarea>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" id="visibility" class="custom-control-input" th:name="visibility">
<label class="custom-control-label" for="visibility">Private Gruppe</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="sel1"></label>
<select class="form-control" id="sel1">
<option selected="true" disabled>--Bitte Veranstaltung auswählen--</option>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
</select>
</div>
<div class="form-group pt-4">
<button class="btn btn-primary" type="submit" style="background: #52a1eb; border-style: none">Erstellen</button> <button class="btn btn-primary" type="submit" style="background: #52a1eb; border-style: none">Erstellen</button>
</div> </div>
</div> </div>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}">
<head>
<meta charset="utf-8">
<title>Gruppendetails</title>
<th:block th:fragment="headcontent">
<!-- Links, Skripts, Styles hier einfügen! -->
</th:block>
</head>
<body>
<header>
<nav class="navigation navigation-secondary" is="mops-navigation" th:fragment="navigation">
<ul>
<li class="active">
<a th:href="@{/gruppen2}" href="/">Gruppen</a>
</li>
<li>
<a th:href="@{/gruppen2/createGroup}" href="/createGroup">Erstellen</a>
</li>
<li>
<a th:href="@{/gruppen2/findGroup}" href="/findGroup">Suche</a>
</li>
</ul>
</nav>
</header>
<main th:fragment="bodycontent">
<div class="container-fluid">
<div class="row">
<div class="col-9" style="border: 10px solid aliceblue; background: aliceblue">
<form action="/" method="get">
<h1 style="color: dodgerblue; font-weight: bold" th:text="${group.getTitle()}"></h1>
<p style="font-weight: bold">
<span class="badge badge-pill badge-dark" style="background: darkslategray" th:if="${group.getVisibility() == group.getVisibility().PRIVATE }">Private Gruppe</span>
<span class="badge badge-pill badge-primary" th:if="${group.getVisibility() == group.getVisibility().PUBLIC}">Öffentliche Gruppe</span>
<span class="badge badge-pill badge-success" style="background: lightseagreen" th:if="${group.getType() == group.getType().LECTURE}"> Veranstaltung</span>
</p>
<p th:text="${group.getDescription()}"></p>
<div class="form-group">
<div class="text-right">
<button class="btn btn-danger" type="danger" style="border-style: none;">Gruppe verlassen</button>
</div>
</div>
</form>
</div>
<div class="col-3" style="white-space: nowrap">
<div>
<h2 style="display: inline-block; margin: 0">Mitglieder</h2>
<button class="btn btn-secondary" type="warning" style="background: slategrey; float: right" >Mitglieder bearbeiten</button>
<p></p>
</div>
<div>
<ul th:each="member : ${group.getMembers()}" class="list-group-flush" style="background: slategrey">
<li class="list-group-item" style="background: aliceblue">
<span th:text="${member.getUser_id()}"></span>
<span th:if="${role == role.ADMIN}" class="badge badge-success">admin</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}">
<head>
<meta charset="utf-8">
<title>Gruppendetails</title>
<th:block th:fragment="headcontent">
<!-- Links, Skripts, Styles hier einfügen! -->
</th:block>
</head>
<body>
<header>
<nav class="navigation navigation-secondary" is="mops-navigation" th:fragment="navigation">
<ul>
<li class="active">
<a th:href="@{/gruppen2}" href="/">Gruppen</a>
</li>
<li>
<a th:href="@{/gruppen2/createGroup}" href="/createGroup">Erstellen</a>
</li>
<li>
<a th:href="@{/gruppen2/findGroup}" href="/findGroup">Suche</a>
</li>
</ul>
</nav>
</header>
<main th:fragment="bodycontent">
</main>
</body>
</html>

View File

@ -3,7 +3,7 @@
th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}"> th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Name des Subsystems</title> <title>Eigene Gruppen</title>
<th:block th:fragment="headcontent"> <th:block th:fragment="headcontent">
<!-- Links, Skripts, Styles hier einfügen! --> <!-- Links, Skripts, Styles hier einfügen! -->
</th:block> </th:block>
@ -25,13 +25,15 @@
</nav> </nav>
</header> </header>
<main th:fragment="bodycontent"> <main th:fragment="bodycontent">
<h1>Meine Gruppen</h1>
<div class="container-fluid"> <div class="container-fluid">
<div class="row" >
<div class="col-10">
<h1>Meine Gruppen</h1>
<form action="/" method="get"> <form action="/" method="get">
<div th:each="gruppe: ${gruppen}"> <div th:each="gruppe: ${gruppen}">
<div style="border: 10px solid aliceblue; background: aliceblue"> <div style="border: 10px solid aliceblue; background: aliceblue">
<h3> <h3>
<a href="url" style="color: dodgerblue; font-weight: bold" th:text="${gruppe.getTitle()}"></a> <a th:href="@{/gruppen2/details(id=${gruppe.getId()})}" style="color: dodgerblue; font-weight: bold" th:text="${gruppe.getTitle()}"></a>
</h3> </h3>
<p th:text="${gruppe.getDescription()}"></p> <p th:text="${gruppe.getDescription()}"></p>
</div> </div>
@ -39,6 +41,24 @@
</div> </div>
</form> </form>
</div> </div>
<div class="col-2" >
<div class="card" style="background: lightgrey">
<div class="card-body">
<h2 class="card-title" th:text="${user.getUser_id()}" style="text-align: center">user_id</h2>
<h3 class="card-text">
<span th:text="${user.getGivenname()}">username</span>
<span th:text="${user.getFamilyname()}">usersurname</span>
</h3>
<p class="card-text" th:text="${user.getEmail()}">usermail</p>
<p>
<small class="card-text">In Gruppen:</small>
<small class="card-text" th:text="${gruppen.size()}"></small>
</p>
</div>
</div>
</div>
</div>
</div>
</main> </main>
</body> </body>
</html> </html>

View File

@ -3,7 +3,7 @@
th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}"> th:replace="~{mopslayout :: html(name='Gruppenbildung', headcontent=~{:: headcontent}, navigation=~{:: navigation}, bodycontent=~{:: bodycontent})}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Name des Subsystems</title> <title>Suche</title>
<th:block th:fragment="headcontent"> <th:block th:fragment="headcontent">
<!-- Links, Skripts, Styles hier einfügen! --> <!-- Links, Skripts, Styles hier einfügen! -->
</th:block> </th:block>
@ -29,19 +29,19 @@
<div class="row"> <div class="row">
<h1>Gruppensuche</h1> <h1>Gruppensuche</h1>
<div class="container-fluid"> <div class="container-fluid">
<form action="/findGroup" method="get"> <form action="/gruppen2/findGroup" method="get">
<div style="border: 10px solid aliceblue; background: aliceblue"> <div style="border: 10px solid aliceblue; background: aliceblue">
<div class="form-group"> <div class="form-group">
<label>Suchbegriff:</label> <label for="suchleiste">Suchbegriff:</label>
<input class="form-control" type="text" value="" name="suchbegriff" placeholder="z.B. Programmieren, Lerngruppe, ..."> <input id="suchleiste" class="form-control" placeholder="z.B. Programmieren, Lerngruppe, ..." th:name="suchbegriff" type="text">
</div> </div>
<button type="button" class="btn btn-primary" style="background: #52a1eb; border-style: none">Suchen</button> <button type="submit" class="btn btn-primary" style="background: #52a1eb; border-style: none">Suchen</button>
</div> </div>
</form> </form>
<br> <br>
<table class="table"> <table class="table">
<!-- Erscheint dann, wenn man "Suchen" Button klickt und Ergebnisse angezeigt werden, aber so solls aussehen --> <!-- Erscheint dann, wenn man "Suchen" Button klickt und Ergebnisse angezeigt werden, aber so solls aussehen -->
<thead> <thead th:if="${!gruppen.isEmpty()}">
<tr> <tr>
<th scope="col">Gruppenname</th> <th scope="col">Gruppenname</th>
<th scope="col">Beschreibung</th> <th scope="col">Beschreibung</th>
@ -49,18 +49,12 @@
<th scope="col">Mitgliederanzahl</th> <th scope="col">Mitgliederanzahl</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody th:each="gruppe : ${gruppen}">
<tr> <tr>
<th scope="row">LA1 Kursgruppe</th> <th scope="row" th:text="${gruppe.title}">Gruppenname</th>
<td>Gruppe für den Kurs LA1</td> <td th:text="${gruppe.getDescription()}">Beschreibung</td>
<td>Öffentlich</td> <td th:text="${gruppe.getVisibility()}">Öffentlich</td>
<td>318</td> <td th:text="${gruppe.getMembers().size()}">Mitgliederanzahl</td>
</tr>
<tr>
<th scope="row">Lerngruppe LA1</th>
<td>Lerngruppe zur Bearbeitung von Übungszetteln</td>
<td>Privat</td>
<td>6</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -6,6 +6,10 @@ import mops.gruppen2.domain.Visibility;
import mops.gruppen2.domain.event.AddUserEvent; import mops.gruppen2.domain.event.AddUserEvent;
import mops.gruppen2.domain.event.CreateGroupEvent; import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.domain.event.Event; import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.GroupType;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.domain.event.AddUserEvent;
import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.repository.EventRepository; import mops.gruppen2.repository.EventRepository;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -105,4 +109,19 @@ class EventServiceTest {
assertEquals(eventService.checkGroup(), 1); assertEquals(eventService.checkGroup(), 1);
} }
@Test
void getDTOOffentlichTest(){
CreateGroupEvent createGroupEvent = new CreateGroupEvent(eventService.checkGroup(), "test", null , GroupType.LECTURE, Visibility.PUBLIC);
EventDTO eventDTO = eventService.getDTO(createGroupEvent);
assertEquals(eventDTO.isVisibility(), true);
}
@Test
void getDTOPrivatTest(){
AddUserEvent addUserEvent = new AddUserEvent(eventService.checkGroup(), "test","franz","mueller","a@a");
EventDTO eventDTO = eventService.getDTO(addUserEvent);
assertEquals(eventDTO.isVisibility(), false);
}
} }