1

Merge pull request #20 from ChUrl/dev

Update Master
This commit is contained in:
Christoph
2020-04-22 15:17:01 +02:00
committed by GitHub
183 changed files with 6782 additions and 4756 deletions

1
.gitignore vendored
View File

@ -34,3 +34,4 @@ out/
.flooignore .flooignore
/mysql/db/storage/ /mysql/db/storage/
/mysql/keycloak/

View File

@ -8,6 +8,43 @@ Private Gruppen kann man nur über einen Beitrittslink beitreten.
Öffentliche Gruppen kann man ohne diesen beitreten. Öffentliche Gruppen kann man ohne diesen beitreten.
Man kann nach Öffentlichen Gruppen über eine Suchfunktion suchen. Man kann nach Öffentlichen Gruppen über eine Suchfunktion suchen.
== Stand
=== Probleme
* Momentan ist kein Controller getestet
* Das Styling ist inkonsistent und skaliert nicht gut auf kleineren screens
* Schlecht dokumentiert und Arc42 nicht aktuell
* Integrationen nicht implementiert
* Gruppenoptionen nicht implementiert
* Snapshotting nicht implementiert
* Caching funktioniert, aber teilweise nicht konsequent verwendet
* Gruppenbeschreibung mit markup nicht implementiert
* Invitelink kann nicht regeneriert werden
* Seit Implementierung eines Caches ist die API kaputt
=== Fertig
* Fast die komplette Logik + Templates + Controller überholt
* Verwendung eines Caches anstatt vieler Datenbankanfragen
* UI: Templates mit Fragmenten und auslagertem styling vereinfacht, einige Seiten zusammengefasst
* Services komplett umstrukturiert, ergeben inhaltlich mehr Sinn und sind kleiner
* Konsequenteres Logging, aspektorientiertes Logging
* Aktivere Gruppenobjekte mit mehr Validierung
* Extratabelle für Invitelinks entfernt, Datenbank vereinfacht
* Man kann Änderungen an Gruppen nachvollziehen mit Zeitstempeln und einer Übersichtsseite
* Gruppen und Teilnehmerlisten können exportiert werden
=== Heroku
Die letzte Version der Anwendung ist unter gruppenfindung.herokuapp.com zu erreichen.
Es existieren zwei Defaultuser:
* Username: orga, Passwort: orga
* Username: studentin, Passwort: studentin
== Ursprüngliche Readme
=== Problem === Problem
Die meisten Teilsysteme von MOPS arbeiten mit Gruppierungen von Studenten: Materialien für Lerngruppen/Veranstaltungen, Gruppenportfolios, Gruppenabstimmungen etc. Die meisten Teilsysteme von MOPS arbeiten mit Gruppierungen von Studenten: Materialien für Lerngruppen/Veranstaltungen, Gruppenportfolios, Gruppenabstimmungen etc.

View File

@ -1,11 +1,9 @@
import com.github.spotbugs.SpotBugsTask
plugins { plugins {
id 'org.springframework.boot' version '2.2.5.RELEASE' id 'org.springframework.boot' version '2.2.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java' id 'java'
id 'com.github.spotbugs' version '3.0.0' id 'com.github.spotbugs' version '4.0.1'
id 'checkstyle' id 'checkstyle'
id 'pmd' id 'pmd'
} }
@ -14,34 +12,43 @@ group = 'mops'
version = '0.0.1-SNAPSHOT' version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11' sourceCompatibility = '11'
repositories {
maven {
url = 'https://s3.cs.hhu.de/public/mops/'
metadataSources {
artifact()
}
}
mavenCentral()
}
spotbugs { spotbugs {
toolVersion = '4.0.1'
ignoreFailures = false ignoreFailures = false
reportLevel = "high" reportLevel = "high"
effort = "max" effort = "max"
toolVersion = '4.0.0-RC1' showProgress = true
} }
tasks.withType(SpotBugsTask) { spotbugsMain {
reports { reports {
xml.enabled = false html {
html.enabled = true enabled = true
}
} }
} }
pmd { pmd {
consoleOutput = true consoleOutput = true
ignoreFailures = true ignoreFailures = true
toolVersion = "6.21.0" toolVersion = "6.22.0"
rulePriority = 5 rulePriority = 5
ruleSets = ["category/java/errorprone.xml", ruleSetFiles = files("config/pmd/ruleset.xml")
"category/java/bestpractices.xml", ruleSets = []
"category/java/security.xml",
"category/java/performance.xml",
"category/java/design.xml"]
} }
checkstyle { checkstyle {
toolVersion = "8.28" toolVersion = "8.30"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml") configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
ignoreFailures = true ignoreFailures = true
} }
@ -56,25 +63,18 @@ configurations {
} }
} }
repositories {
maven {
url = 'https://s3.cs.hhu.de/public/mops/'
metadataSources {
artifact()
}
}
mavenCentral()
}
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.security.oauth:spring-security-oauth2:2.4.0.RELEASE' implementation 'org.springframework.security.oauth:spring-security-oauth2:2.4.0.RELEASE'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.keycloak:keycloak-spring-boot-starter:9.0.0' implementation 'org.keycloak:keycloak-spring-boot-starter:9.0.0'
implementation 'org.keycloak.bom:keycloak-adapter-bom:9.0.0' implementation 'org.keycloak.bom:keycloak-adapter-bom:9.0.0'
implementation 'mops:styleguide:2.1.0' implementation 'mops:styleguide:2.1.0'
@ -82,10 +82,11 @@ dependencies {
implementation 'io.springfox:springfox-swagger-ui:2.9.2' implementation 'io.springfox:springfox-swagger-ui:2.9.2'
implementation 'com.github.javafaker:javafaker:1.0.2' implementation 'com.github.javafaker:javafaker:1.0.2'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.10.3' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.10.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3'
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2' runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java' runtimeOnly 'mysql:mysql-connector-java'

View File

@ -223,7 +223,7 @@
<property name="braceAdjustment" value="0"/> <property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/> <property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/> <property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="8"/> <!--<property name="lineWrappingIndentation" value="8"/>-->
<property name="arrayInitIndent" value="4"/> <property name="arrayInitIndent" value="4"/>
</module> </module>
<!-- <module name="VariableDeclarationUsageDistance"/>--> <!-- <module name="VariableDeclarationUsageDistance"/>-->

335
config/pmd/ruleset.xml Normal file
View File

@ -0,0 +1,335 @@
<ruleset name="quickstart"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>PMD Rules</description>
<!-- BEST PRACTICES -->
<!-- <rule ref="category/java/bestpractices.xml/AbstractClassWithoutAbstractMethod" /> -->
<!-- <rule ref="category/java/bestpractices.xml/AccessorClassGeneration" /> -->
<!-- <rule ref="category/java/bestpractices.xml/AccessorMethodGeneration" /> -->
<!-- <rule ref="category/java/bestpractices.xml/ArrayIsStoredDirectly" /> -->
<!-- <rule ref="category/java/bestpractices.xml/AvoidPrintStackTrace" /> -->
<!-- <rule ref="category/java/bestpractices.xml/AvoidReassigningParameters" /> -->
<rule ref="category/java/bestpractices.xml/AvoidStringBufferField"/>
<rule ref="category/java/bestpractices.xml/AvoidUsingHardCodedIP"/>
<rule ref="category/java/bestpractices.xml/CheckResultSet"/>
<rule ref="category/java/bestpractices.xml/ConstantsInInterface"/>
<rule ref="category/java/bestpractices.xml/DefaultLabelNotLastInSwitchStmt"/>
<rule ref="category/java/bestpractices.xml/ForLoopCanBeForeach"/>
<!-- <rule ref="category/java/bestpractices.xml/GuardLogStatement"/>-->
<!-- <rule ref="category/java/bestpractices.xml/JUnit4SuitesShouldUseSuiteAnnotation" /> -->
<!-- <rule ref="category/java/bestpractices.xml/JUnit4TestShouldUseAfterAnnotation" /> -->
<!-- <rule ref="category/java/bestpractices.xml/JUnit4TestShouldUseBeforeAnnotation" /> -->
<!-- <rule ref="category/java/bestpractices.xml/JUnit4TestShouldUseTestAnnotation" /> -->
<!-- <rule ref="category/java/bestpractices.xml/JUnitAssertionsShouldIncludeMessage" /> -->
<!-- <rule ref="category/java/bestpractices.xml/JUnitTestContainsTooManyAsserts" /> -->
<!-- <rule ref="category/java/bestpractices.xml/JUnitTestsShouldIncludeAssert" /> -->
<!-- <rule ref="category/java/bestpractices.xml/JUnitUseExpected" /> -->
<rule ref="category/java/bestpractices.xml/LooseCoupling"/>
<!-- <rule ref="category/java/bestpractices.xml/MethodReturnsInternalArray" /> -->
<rule ref="category/java/bestpractices.xml/MissingOverride"/>
<rule ref="category/java/bestpractices.xml/OneDeclarationPerLine"/>
<rule ref="category/java/bestpractices.xml/PositionLiteralsFirstInCaseInsensitiveComparisons"/>
<rule ref="category/java/bestpractices.xml/PositionLiteralsFirstInComparisons"/>
<rule ref="category/java/bestpractices.xml/PreserveStackTrace"/>
<!-- <rule ref="category/java/bestpractices.xml/ReplaceEnumerationWithIterator" /> -->
<!-- <rule ref="category/java/bestpractices.xml/ReplaceHashtableWithMap" /> -->
<!-- <rule ref="category/java/bestpractices.xml/ReplaceVectorWithList" /> -->
<rule ref="category/java/bestpractices.xml/SwitchStmtsShouldHaveDefault"/>
<!-- <rule ref="category/java/bestpractices.xml/SystemPrintln" /> -->
<rule ref="category/java/bestpractices.xml/UnusedFormalParameter"/>
<rule ref="category/java/bestpractices.xml/UnusedImports"/>
<rule ref="category/java/errorprone.xml/ImportFromSamePackage"/>
<rule ref="category/java/bestpractices.xml/UnusedLocalVariable"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateField"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateMethod"/>
<rule ref="category/java/bestpractices.xml/UseAssertEqualsInsteadOfAssertTrue"/>
<rule ref="category/java/bestpractices.xml/UseAssertNullInsteadOfAssertTrue"/>
<rule ref="category/java/bestpractices.xml/UseAssertSameInsteadOfAssertTrue"/>
<rule ref="category/java/bestpractices.xml/UseAssertTrueInsteadOfAssertEquals"/>
<rule ref="category/java/bestpractices.xml/UseCollectionIsEmpty"/>
<!-- <rule ref="category/java/bestpractices.xml/UseVarargs" /> -->
<!-- <rule ref="category/java/codestyle.xml/AtLeastOneConstructor" /> -->
<rule ref="category/java/codestyle.xml/AvoidDollarSigns"/>
<!-- <rule ref="category/java/codestyle.xml/AvoidFinalLocalVariable" /> -->
<rule ref="category/java/codestyle.xml/AvoidProtectedFieldInFinalClass"/>
<rule ref="category/java/codestyle.xml/AvoidProtectedMethodInFinalClassNotExtending"/>
<!-- NAMING CONVENTIONS -->
<rule ref="category/java/codestyle.xml/FormalParameterNamingConventions"/>
<rule ref="category/java/codestyle.xml/ClassNamingConventions"/>
<rule ref="category/java/codestyle.xml/LocalVariableNamingConventions"/>
<rule ref="category/java/codestyle.xml/MethodNamingConventions"/>
<rule ref="category/java/codestyle.xml/PackageCase"/>
<!-- UNIMPLEMENTED -->
<!-- <rule ref="category/java/codestyle.xml/FieldNamingConventions" /> -->
<rule ref="category/java/codestyle.xml/GenericsNaming"/>
<!-- <rule ref="category/java/codestyle.xml/LongVariable" /> -->
<rule ref="category/java/codestyle.xml/ShortClassName"/>
<rule ref="category/java/codestyle.xml/ShortMethodName"/>
<rule ref="category/java/codestyle.xml/ShortVariable"/>
<!-- OTHER -->
<!-- <rule ref="category/java/codestyle.xml/AvoidUsingNativeCode" /> -->
<!-- <rule ref="category/java/codestyle.xml/BooleanGetMethodName" /> -->
<!-- <rule ref="category/java/codestyle.xml/CallSuperInConstructor" /> -->
<!-- <rule ref="category/java/codestyle.xml/CommentDefaultAccessModifier" /> -->
<!-- <rule ref="category/java/codestyle.xml/ConfusingTernary" /> -->
<rule ref="category/java/codestyle.xml/ControlStatementBraces"/>
<!-- <rule ref="category/java/codestyle.xml/DefaultPackage" /> -->
<rule ref="category/java/codestyle.xml/DontImportJavaLang"/>
<rule ref="category/java/codestyle.xml/DuplicateImports"/>
<!-- <rule ref="category/java/codestyle.xml/EmptyMethodInAbstractClassShouldBeAbstract" /> -->
<rule ref="category/java/codestyle.xml/ExtendsObject"/>
<!-- <rule ref="category/java/codestyle.xml/FieldDeclarationsShouldBeAtStartOfClass" /> -->
<rule ref="category/java/codestyle.xml/ForLoopShouldBeWhileLoop"/>
<rule ref="category/java/codestyle.xml/IdenticalCatchBranches"/>
<!-- <rule ref="category/java/codestyle.xml/LocalVariableCouldBeFinal" /> -->
<!-- <rule ref="category/java/codestyle.xml/MethodArgumentCouldBeFinal" /> -->
<!-- <rule ref="category/java/codestyle.xml/MIsLeadingVariableName" /> -->
<rule ref="category/java/codestyle.xml/NoPackage"/>
<!-- <rule ref="category/java/codestyle.xml/OnlyOneReturn" /> -->
<!-- <rule ref="category/java/codestyle.xml/PrematureDeclaration" /> -->
<!-- <rule ref="category/java/codestyle.xml/SuspiciousConstantFieldName" /> -->
<!-- <rule ref="category/java/codestyle.xml/TooManyStaticImports" /> -->
<rule ref="category/java/codestyle.xml/UnnecessaryAnnotationValueElement"/>
<rule ref="category/java/codestyle.xml/UnnecessaryConstructor"/>
<rule ref="category/java/codestyle.xml/UnnecessaryFullyQualifiedName"/>
<rule ref="category/java/codestyle.xml/UnnecessaryLocalBeforeReturn"/>
<rule ref="category/java/codestyle.xml/UnnecessaryModifier"/>
<rule ref="category/java/codestyle.xml/UnnecessaryReturn"/>
<rule ref="category/java/codestyle.xml/UselessParentheses"/>
<rule ref="category/java/codestyle.xml/UselessQualifiedThis"/>
<rule ref="category/java/design.xml/AbstractClassWithoutAnyMethod"/>
<!-- <rule ref="category/java/design.xml/AvoidCatchingGenericException" /> -->
<!-- <rule ref="category/java/design.xml/AvoidDeeplyNestedIfStmts" /> -->
<!-- <rule ref="category/java/design.xml/AvoidRethrowingException" /> -->
<!-- <rule ref="category/java/design.xml/AvoidThrowingNewInstanceOfSameException" /> -->
<!-- <rule ref="category/java/design.xml/AvoidThrowingNullPointerException" /> -->
<!-- <rule ref="category/java/design.xml/AvoidThrowingRawExceptionTypes" /> -->
<rule ref="category/java/design.xml/ClassWithOnlyPrivateConstructorsShouldBeFinal"/>
<!-- <rule ref="category/java/design.xml/CollapsibleIfStatements" /> -->
<rule ref="category/java/design.xml/CouplingBetweenObjects"/>
<!-- <rule ref="category/java/design.xml/DataClass" /> -->
<!-- <rule ref="category/java/design.xml/DoNotExtendJavaLangError" /> -->
<!-- <rule ref="category/java/design.xml/ExceptionAsFlowControl" /> -->
<!-- <rule ref="category/java/design.xml/ExcessiveClassLength" /> -->
<!-- <rule ref="category/java/design.xml/ExcessiveImports" /> -->
<rule ref="category/java/design.xml/ExcessiveMethodLength"/>
<rule ref="category/java/design.xml/ExcessiveParameterList"/>
<rule ref="category/java/design.xml/ExcessivePublicCount"/>
<rule ref="category/java/design.xml/FinalFieldCouldBeStatic"/>
<!-- <rule ref="category/java/design.xml/GodClass" /> -->
<!-- <rule ref="category/java/design.xml/ImmutableField" /> -->
<!-- <rule ref="category/java/design.xml/LawOfDemeter"/>-->
<rule ref="category/java/design.xml/LogicInversion"/>
<rule ref="category/java/design.xml/CyclomaticComplexity"/>
<!-- <rule ref="category/java/design.xml/NcssCount" /> -->
<rule ref="category/java/design.xml/NPathComplexity"/>
<!-- <rule ref="category/java/design.xml/SignatureDeclareThrowsException" /> -->
<rule ref="category/java/design.xml/SimplifiedTernary"/>
<!-- <rule ref="category/java/design.xml/SimplifyBooleanAssertion" /> -->
<!-- <rule ref="category/java/design.xml/SimplifyBooleanExpressions" /> -->
<rule ref="category/java/design.xml/SimplifyBooleanReturns"/>
<rule ref="category/java/design.xml/SimplifyConditional"/>
<rule ref="category/java/design.xml/SingularField"/>
<!-- <rule ref="category/java/design.xml/SwitchDensity" /> -->
<rule ref="category/java/design.xml/TooManyFields"/>
<rule ref="category/java/design.xml/TooManyMethods"/>
<rule ref="category/java/design.xml/UselessOverridingMethod"/>
<!-- <rule ref="category/java/design.xml/UseObjectForClearerAPI" /> -->
<rule ref="category/java/design.xml/UseUtilityClass"/>
<!-- <rule ref="category/java/documentation.xml/CommentContent" /> -->
<!-- <rule ref="category/java/documentation.xml/CommentRequired" /> -->
<!-- <rule ref="category/java/documentation.xml/CommentSize" /> -->
<rule ref="category/java/documentation.xml/UncommentedEmptyConstructor"/>
<rule ref="category/java/documentation.xml/UncommentedEmptyMethodBody"/>
<rule ref="category/java/errorprone.xml/AssignmentInOperand">
<properties>
<property name="allowWhile" value="true"/>
</properties>
</rule>
<rule ref="category/java/errorprone.xml/AssignmentToNonFinalStatic"/>
<rule ref="category/java/errorprone.xml/AvoidAccessibilityAlteration"/>
<!-- <rule ref="category/java/errorprone.xml/AvoidAssertAsIdentifier" /> -->
<rule ref="category/java/errorprone.xml/AvoidBranchingStatementAsLastInLoop"/>
<!-- <rule ref="category/java/errorprone.xml/AvoidCallingFinalize" /> -->
<!-- <rule ref="category/java/errorprone.xml/AvoidCatchingNPE" /> -->
<rule ref="category/java/errorprone.xml/AvoidCatchingThrowable"/>
<rule ref="category/java/errorprone.xml/AvoidDecimalLiteralsInBigDecimalConstructor"/>
<!-- <rule ref="category/java/errorprone.xml/AvoidDuplicateLiterals" /> -->
<!-- <rule ref="category/java/errorprone.xml/AvoidEnumAsIdentifier" /> -->
<!-- <rule ref="category/java/errorprone.xml/AvoidFieldNameMatchingMethodName" /> -->
<!-- <rule ref="category/java/errorprone.xml/AvoidFieldNameMatchingTypeName" /> -->
<rule ref="category/java/errorprone.xml/AvoidInstanceofChecksInCatchClause"/>
<!-- <rule ref="category/java/errorprone.xml/AvoidLiteralsInIfCondition" /> -->
<!-- <rule ref="category/java/errorprone.xml/AvoidLosingExceptionInformation" /> -->
<rule ref="category/java/errorprone.xml/AvoidMultipleUnaryOperators"/>
<rule ref="category/java/errorprone.xml/AvoidUsingOctalValues"/>
<rule ref="category/java/errorprone.xml/BadComparison"/>
<!-- <rule ref="category/java/errorprone.xml/BeanMembersShouldSerialize" /> -->
<rule ref="category/java/errorprone.xml/BrokenNullCheck"/>
<!-- <rule ref="category/java/errorprone.xml/CallSuperFirst" /> -->
<!-- <rule ref="category/java/errorprone.xml/CallSuperLast" /> -->
<rule ref="category/java/errorprone.xml/CheckSkipResult"/>
<rule ref="category/java/errorprone.xml/ClassCastExceptionWithToArray"/>
<rule ref="category/java/errorprone.xml/CloneMethodMustBePublic"/>
<rule ref="category/java/errorprone.xml/CloneMethodMustImplementCloneable"/>
<rule ref="category/java/errorprone.xml/CloneMethodReturnTypeMustMatchClassName"/>
<rule ref="category/java/errorprone.xml/CloneThrowsCloneNotSupportedException"/>
<rule ref="category/java/errorprone.xml/CloseResource"/>
<rule ref="category/java/errorprone.xml/CompareObjectsWithEquals"/>
<!-- <rule ref="category/java/errorprone.xml/ConstructorCallsOverridableMethod" /> -->
<!-- <rule ref="category/java/errorprone.xml/DataflowAnomalyAnalysis" /> -->
<rule ref="category/java/errorprone.xml/DoNotCallGarbageCollectionExplicitly"/>
<!-- <rule ref="category/java/errorprone.xml/DoNotCallSystemExit" /> -->
<rule ref="category/java/errorprone.xml/DoNotExtendJavaLangThrowable"/>
<rule ref="category/java/design.xml/DoNotExtendJavaLangError"/>
<!-- <rule ref="category/java/errorprone.xml/DoNotHardCodeSDCard" /> -->
<!-- <rule ref="category/java/errorprone.xml/DoNotThrowExceptionInFinally" /> -->
<!-- <rule ref="category/java/errorprone.xml/DontImportSun" /> -->
<rule ref="category/java/errorprone.xml/DontUseFloatTypeForLoopIndices"/>
<rule ref="category/java/errorprone.xml/EmptyCatchBlock"/>
<!-- EMPTY RULES -->
<rule ref="category/java/errorprone.xml/EmptyFinalizer"/>
<rule ref="category/java/errorprone.xml/EmptyFinallyBlock"/>
<rule ref="category/java/errorprone.xml/EmptyIfStmt"/>
<rule ref="category/java/errorprone.xml/EmptyInitializer"/>
<rule ref="category/java/errorprone.xml/EmptyStatementBlock"/>
<rule ref="category/java/errorprone.xml/EmptyStatementNotInLoop"/>
<rule ref="category/java/errorprone.xml/EmptySwitchStatements"/>
<rule ref="category/java/errorprone.xml/EmptySynchronizedBlock"/>
<rule ref="category/java/errorprone.xml/EmptyTryBlock"/>
<rule ref="category/java/errorprone.xml/EmptyWhileStmt"/>
<rule ref="category/java/errorprone.xml/EqualsNull"/>
<!-- <rule ref="category/java/errorprone.xml/FinalizeDoesNotCallSuperFinalize" /> -->
<!-- <rule ref="category/java/errorprone.xml/FinalizeOnlyCallsSuperFinalize" /> -->
<!-- <rule ref="category/java/errorprone.xml/FinalizeOverloaded" /> -->
<!-- <rule ref="category/java/errorprone.xml/FinalizeShouldBeProtected" /> -->
<rule ref="category/java/errorprone.xml/IdempotentOperations"/>
<rule ref="category/java/errorprone.xml/InstantiationToGetClass"/>
<!-- <rule ref="category/java/errorprone.xml/InvalidSlf4jMessageFormat" /> -->
<rule ref="category/java/errorprone.xml/JumbledIncrementer"/>
<!-- <rule ref="category/java/errorprone.xml/JUnitSpelling" /> -->
<!-- <rule ref="category/java/errorprone.xml/JUnitStaticSuite" /> -->
<!-- <rule ref="category/java/errorprone.xml/LoggerIsNotStaticFinal" /> -->
<!-- <rule ref="category/java/errorprone.xml/MethodWithSameNameAsEnclosingClass" /> -->
<rule ref="category/java/errorprone.xml/MisplacedNullCheck"/>
<rule ref="category/java/errorprone.xml/MissingBreakInSwitch"/>
<!-- <rule ref="category/java/errorprone.xml/MissingSerialVersionUID" /> -->
<rule ref="category/java/errorprone.xml/MissingStaticMethodInNonInstantiatableClass"/>
<!-- <rule ref="category/java/errorprone.xml/MoreThanOneLogger" /> -->
<rule ref="category/java/errorprone.xml/NonCaseLabelInSwitchStatement"/>
<rule ref="category/java/errorprone.xml/NonStaticInitializer"/>
<!-- <rule ref="category/java/errorprone.xml/NullAssignment" /> -->
<rule ref="category/java/errorprone.xml/OverrideBothEqualsAndHashcode"/>
<rule ref="category/java/errorprone.xml/ProperCloneImplementation"/>
<rule ref="category/java/errorprone.xml/ProperLogger"/>
<rule ref="category/java/errorprone.xml/ReturnEmptyArrayRatherThanNull"/>
<rule ref="category/java/errorprone.xml/ReturnFromFinallyBlock"/>
<!-- <rule ref="category/java/errorprone.xml/SimpleDateFormatNeedsLocale" /> -->
<rule ref="category/java/errorprone.xml/SingleMethodSingleton"/>
<rule ref="category/java/errorprone.xml/SingletonClassReturningNewInstance"/>
<!-- <rule ref="category/java/errorprone.xml/StaticEJBFieldShouldBeFinal" /> -->
<!-- <rule ref="category/java/errorprone.xml/StringBufferInstantiationWithChar" /> -->
<rule ref="category/java/errorprone.xml/SuspiciousEqualsMethodName"/>
<rule ref="category/java/errorprone.xml/SuspiciousHashcodeMethodName"/>
<rule ref="category/java/errorprone.xml/SuspiciousOctalEscape"/>
<!-- <rule ref="category/java/errorprone.xml/TestClassWithoutTestCases" /> -->
<rule ref="category/java/errorprone.xml/UnconditionalIfStatement"/>
<!-- <rule ref="category/java/errorprone.xml/UnnecessaryBooleanAssertion" /> -->
<!-- <rule ref="category/java/errorprone.xml/UnnecessaryCaseChange" /> -->
<rule ref="category/java/errorprone.xml/UnnecessaryConversionTemporary"/>
<rule ref="category/java/errorprone.xml/UnusedNullCheckInEquals"/>
<!-- <rule ref="category/java/errorprone.xml/UseCorrectExceptionLogging" /> -->
<rule ref="category/java/errorprone.xml/UseEqualsToCompareStrings"/>
<rule ref="category/java/errorprone.xml/UselessOperationOnImmutable"/>
<rule ref="category/java/errorprone.xml/UseLocaleWithCaseConversions"/>
<!-- <rule ref="category/java/errorprone.xml/UseProperClassLoader" /> -->
<!-- <rule ref="category/java/multithreading.xml/AvoidSynchronizedAtMethodLevel" /> -->
<rule ref="category/java/multithreading.xml/AvoidThreadGroup"/>
<rule ref="category/java/multithreading.xml/AvoidUsingVolatile"/>
<!-- <rule ref="category/java/multithreading.xml/DoNotUseThreads" /> -->
<rule ref="category/java/multithreading.xml/DontCallThreadRun"/>
<rule ref="category/java/multithreading.xml/DoubleCheckedLocking"/>
<rule ref="category/java/multithreading.xml/NonThreadSafeSingleton"/>
<!-- <rule ref="category/java/multithreading.xml/UnsynchronizedStaticDateFormatter"/>-->
<!-- <rule ref="category/java/multithreading.xml/UseConcurrentHashMap" /> -->
<rule ref="category/java/multithreading.xml/UseNotifyAllInsteadOfNotify"/>
<!-- <rule ref="category/java/performance.xml/AddEmptyString" /> -->
<!-- <rule ref="category/java/performance.xml/AppendCharacterWithChar" /> -->
<!-- <rule ref="category/java/performance.xml/AvoidArrayLoops" /> -->
<!-- <rule ref="category/java/performance.xml/AvoidFileStream" /> -->
<!-- <rule ref="category/java/performance.xml/AvoidInstantiatingObjectsInLoops" /> -->
<!-- <rule ref="category/java/performance.xml/AvoidUsingShortType" /> -->
<rule ref="category/java/performance.xml/BigIntegerInstantiation"/>
<rule ref="category/java/performance.xml/BooleanInstantiation"/>
<!-- <rule ref="category/java/performance.xml/ByteInstantiation" /> -->
<!-- <rule ref="category/java/performance.xml/ConsecutiveAppendsShouldReuse" /> -->
<!-- <rule ref="category/java/performance.xml/ConsecutiveLiteralAppends" /> -->
<!-- <rule ref="category/java/performance.xml/InefficientEmptyStringCheck" /> -->
<!-- <rule ref="category/java/performance.xml/InefficientStringBuffering" /> -->
<!-- <rule ref="category/java/performance.xml/InsufficientStringBufferDeclaration" /> -->
<!-- <rule ref="category/java/performance.xml/IntegerInstantiation" /> -->
<!-- <rule ref="category/java/performance.xml/LongInstantiation" /> -->
<rule ref="category/java/performance.xml/OptimizableToArrayCall"/>
<!-- <rule ref="category/java/performance.xml/RedundantFieldInitializer" /> -->
<!-- <rule ref="category/java/performance.xml/SimplifyStartsWith" /> -->
<!-- <rule ref="category/java/performance.xml/ShortInstantiation" /> -->
<!-- <rule ref="category/java/performance.xml/StringInstantiation" /> -->
<!-- <rule ref="category/java/performance.xml/StringToString" /> -->
<rule ref="category/java/performance.xml/TooFewBranchesForASwitchStatement"/>
<!-- <rule ref="category/java/performance.xml/UnnecessaryWrapperObjectCreation" /> -->
<!-- <rule ref="category/java/performance.xml/UseArrayListInsteadOfVector" /> -->
<!-- <rule ref="category/java/performance.xml/UseArraysAsList" /> -->
<!-- <rule ref="category/java/performance.xml/UseIndexOfChar" /> -->
<!-- <rule ref="category/java/performance.xml/UselessStringValueOf" /> -->
<!-- <rule ref="category/java/performance.xml/UseStringBufferForStringAppends" /> -->
<!-- <rule ref="category/java/performance.xml/UseStringBufferLength" /> -->
</ruleset>

View File

@ -1,23 +1,50 @@
version: "3.7" version: "3.7"
services: services:
dbmysql: dbmysql:
image: mysql:5.7 image: mysql:8.0
container_name: 'dbmysql' container_name: 'dbmysql'
environment: environment:
MYSQL_DATABASE: 'gruppen2' MYSQL_DATABASE: 'gruppen'
MYSQL_USER: 'root' MYSQL_USER: 'gruppen'
MYSQL_ROOT_PASSWORD: 'geheim' MYSQL_PASSWORD: 'password'
MYSQL_ROOT_PASSWORD: 'root'
restart: always restart: always
volumes: volumes:
- './mysql/db/storage:/var/lib/mysql' - './mysql/db/storage:/var/lib/mysql'
- './mysql/db/entrypoint:/docker-entrypoint-initdb.d/' - './mysql/db/entrypoint:/docker-entrypoint-initdb.d/'
ports:
- '3306:3306' keymysql:
image: mysql:8.0
container_name: 'keymysql'
environment:
MYSQL_DATABASE: 'keycloak'
MYSQL_USER: 'keycloak'
MYSQL_PASSWORD: 'password'
MYSQL_ROOT_PASSWORD: 'root'
volumes:
- './mysql/keycloak/storage:/var/lib/mysql'
keycloak:
image: jboss/keycloak
container_name: 'keycloak'
depends_on:
- keymysql
environment:
DB_VENDOR: 'MYSQL'
DB_ADDR: 'keymysql'
DB_DATABASE: 'keycloak'
DB_USER: 'keycloak'
DB_PASSWORD: 'password'
KEYCLOAK_USER: 'admin'
KEYCLOAK_PASSWORD: 'admin'
ports:
- '8082:8080'
gruppenapp: gruppenapp:
build: . build: .
container_name: 'gruppenapp' container_name: 'gruppenapp'
depends_on: depends_on:
- dbmysql - dbmysql
- keycloak
command: ["/app/wait-for-it.sh", "dbmysql:3306", "--", "java", "-Dspring.profiles.active=docker", "-jar", "/app/gruppen2.jar"] command: ["/app/wait-for-it.sh", "dbmysql:3306", "--", "java", "-Dspring.profiles.active=docker", "-jar", "/app/gruppen2.jar"]
ports: ports:
- '8081:8080' - '8081:8080'

View File

@ -1,20 +0,0 @@
# Bei "schicht" können Sie 'vormittags', 'nachmittags' oder 'egal' eintragen.
gruppe: IT-Bois
url: https://github.com/hhu-propra2/abschlussprojekt-it-bois
names:
- killerber4t
- tomvahl
- AndiBuls
- XXNitram
- LukasEttel
- Mahgs
- ChUrl
- kasch309
notebook: true
schicht: nachmittags
projektauswahl:
- Gruppenbildung
- Punkteübersicht
- Materialsammlung
- Korrektorinnen Bewerbung

2
lombok.config Normal file
View File

@ -0,0 +1,2 @@
lombok.anyConstructor.addConstructorProperties = true
lombok.equalsAndHashCode.callSuper = call

View File

@ -1,15 +1,10 @@
CREATE TABLE event CREATE TABLE event
( (
event_id INT PRIMARY KEY AUTO_INCREMENT, event_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL, group_id VARCHAR(36) NOT NULL,
user_id VARCHAR(50), group_version INT NOT NULL,
event_type VARCHAR(36), exec_id VARCHAR(32) NOT NULL,
event_payload JSON target_id VARCHAR(32),
); event_date DATETIME NOT NULL,
event_payload JSON NOT NULL
CREATE TABLE invite
(
invite_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
invite_link VARCHAR(36) NOT NULL
); );

View File

@ -0,0 +1,12 @@
DROP TABLE IF EXISTS event;
CREATE TABLE event
(
event_id INT PRIMARY KEY AUTO_INCREMENT,
group_id VARCHAR(36) NOT NULL,
group_version INT NOT NULL,
exec_id VARCHAR(32) NOT NULL,
target_id VARCHAR(32),
event_date DATETIME NOT NULL,
event_payload TEXT NOT NULL
);

View File

@ -2,47 +2,11 @@ package mops.gruppen2;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
@SpringBootApplication @SpringBootApplication
@EnableCaching
@EnableSwagger2
public class Gruppen2Application { public class Gruppen2Application {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Gruppen2Application.class, args); SpringApplication.run(Gruppen2Application.class, args);
} }
@Bean
public Docket productAPI() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.paths(PathSelectors.ant("/gruppen2/api/**"))
.apis(RequestHandlerSelectors.basePackage("mops.gruppen2"))
.build()
.apiInfo(apiMetadata());
}
private ApiInfo apiMetadata() {
return new ApiInfo(
"Gruppenbildung API",
"API zum anfragen/aktualisieren der Gruppendaten.",
"0.0.1",
"Free to use",
new Contact("gruppen2", "https://github.com/hhu-propra2/abschlussprojekt-it-bois", ""),
"",
"",
Collections.emptyList()
);
}
} }

View File

@ -0,0 +1,62 @@
package mops.gruppen2.aspect;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.Trace;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Log4j2
@Profile("dev")
@Aspect
@Component
public class LogAspect {
// ######################################### POINTCUT ########################################
@Pointcut("within(@mops.gruppen2.aspect.annotation.TraceMethodCalls *)")
public void beanAnnotatedWithMonitor() {}
@Pointcut("execution(public * *(..))")
public void publicMethod() {}
@Pointcut("publicMethod() && beanAnnotatedWithMonitor()")
public void logMethodCalls() {}
// ###################################### ANNOTATIONS ########################################
@Before("@annotation(mops.gruppen2.aspect.annotation.Trace)")
public static void logCustom(JoinPoint joinPoint) {
log.trace(((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Trace.class).value());
}
@Before("@annotation(mops.gruppen2.aspect.annotation.TraceMethodCall) || logMethodCalls()")
public static void logMethodCall(JoinPoint joinPoint) {
log.trace("Methodenaufruf: {} ({})",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName().replace("mops.gruppen2.", ""));
System.out.println();
}
@Around("@annotation(mops.gruppen2.aspect.annotation.TraceExecutionTime)")
public static Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
joinPoint.proceed();
long stop = System.currentTimeMillis();
log.trace("Ausführungsdauer: {} Millis", stop - start);
return joinPoint.proceed();
}
}

View File

@ -0,0 +1,16 @@
package mops.gruppen2.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Schreibt eine benutzerdefinierte Nachricht in den Trace-Stream bei Methodenaufruf.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trace {
String value();
}

View File

@ -0,0 +1,15 @@
package mops.gruppen2.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Schreibt die Methodenausführdauer in den Trace-Stream.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TraceExecutionTime {
}

View File

@ -0,0 +1,14 @@
package mops.gruppen2.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Schreibt eine Nachricht bei Methodenausführung in den Trace-Stream.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TraceMethodCall {
}

View File

@ -0,0 +1,14 @@
package mops.gruppen2.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Schreibt eine Nachricht für jede ausgeführte Methode einer Klasse in den Trace-Stream.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TraceMethodCalls {
}

View File

@ -0,0 +1,15 @@
package mops.gruppen2.config;
import mops.gruppen2.config.converter.StringToLimitConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class FormatterConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToLimitConverter());
}
}

View File

@ -2,6 +2,7 @@ package mops.gruppen2.config;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -15,15 +16,16 @@ import org.springframework.web.client.RestTemplate;
*/ */
@Configuration @Configuration
@KeycloakConfiguration
public class KeycloakConfig { public class KeycloakConfig {
@Value("${keycloak.resource}") @Value("${keycloak.resource}")
private String clientId; private String clientId;
@Value("${keycloak.credentials.secret}") @Value("2e2e5770-c454-4d31-be99-9d8c34c93089")
private String clientSecret; private String clientSecret;
@Value("${hhu_keycloak.token-uri}") @Value("https://churl-keycloak.herokuapp.com/auth/realms/Gruppen/protocol/openid-connect/token")
private String tokenUri; private String tokenUri;
@Bean @Bean

View File

@ -29,7 +29,7 @@ import javax.servlet.http.HttpServletRequest;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class) @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) { public void configureGlobal(AuthenticationManagerBuilder auth) {

View File

@ -0,0 +1,43 @@
package mops.gruppen2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
@Profile("dev")
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket productAPI() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.paths(PathSelectors.ant("/gruppen2/api/**"))
.apis(RequestHandlerSelectors.basePackage("mops.gruppen2"))
.build()
.apiInfo(apiMetadata());
}
private ApiInfo apiMetadata() {
return new ApiInfo(
"Gruppenbildung API",
"API zum anfragen/aktualisieren der Gruppendaten.",
"0.0.1",
"Free to use",
new Contact("gruppen2", "https://github.com/hhu-propra2/abschlussprojekt-it-bois", ""),
"",
"",
Collections.emptyList()
);
}
}

View File

@ -0,0 +1,12 @@
package mops.gruppen2.config.converter;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import org.springframework.core.convert.converter.Converter;
public class StringToLimitConverter implements Converter<String, Limit> {
@Override
public Limit convert(String value) {
return new Limit(Long.parseLong(value));
}
}

View File

@ -1,74 +0,0 @@
package mops.gruppen2.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.api.GroupRequestWrapper;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.service.APIFormatterService;
import mops.gruppen2.service.EventService;
import mops.gruppen2.service.GroupService;
import mops.gruppen2.service.UserService;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Api zum Datenabgleich mit Gruppenfindung.
*/
//TODO: API-Service?
@RestController
@RequestMapping("/gruppen2/api")
public class APIController {
private final EventService eventService;
private final GroupService groupService;
private final UserService userService;
public APIController(EventService eventService, GroupService groupService, UserService userService) {
this.eventService = eventService;
this.groupService = groupService;
this.userService = userService;
}
@GetMapping("/updateGroups/{lastEventId}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt alle Gruppen zurück, in denen sich etwas geändert hat")
public GroupRequestWrapper updateGroups(@ApiParam("Letzter Status des Anfragestellers") @PathVariable Long lastEventId) throws EventException {
List<Event> events = eventService.getNewEvents(lastEventId);
return APIFormatterService.wrap(eventService.getMaxEventId(), groupService.projectEventList(events));
}
@GetMapping("/getGroupIdsOfUser/{userId}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt alle Gruppen zurück, in denen sich ein Teilnehmer befindet")
public List<String> getGroupIdsOfUser(@ApiParam("Teilnehmer dessen groupIds zurückgegeben werden sollen") @PathVariable String userId) {
return userService.getUserGroups(userId).stream()
.map(group -> group.getId().toString())
.collect(Collectors.toList());
}
@GetMapping("/getGroup/{groupId}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt die Gruppe mit der als Parameter mitgegebenden groupId zurück")
public Group getGroupById(@ApiParam("GruppenId der gefordeten Gruppe") @PathVariable String groupId) throws EventException {
List<Event> eventList = eventService.getEventsOfGroup(UUID.fromString(groupId));
List<Group> groups = groupService.projectEventList(eventList);
if (groups.isEmpty()) {
return null;
}
return groups.get(0);
}
}

View File

@ -1,120 +0,0 @@
package mops.gruppen2.controller;
import mops.gruppen2.domain.Account;
import mops.gruppen2.service.ControllerService;
import mops.gruppen2.service.GroupService;
import mops.gruppen2.service.KeyCloakService;
import mops.gruppen2.service.ValidationService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.annotation.SessionScope;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.security.RolesAllowed;
import java.util.UUID;
@Controller
@SessionScope
@RequestMapping("/gruppen2")
public class GroupCreationController {
private final GroupService groupService;
private final ControllerService controllerService;
private final ValidationService validationService;
public GroupCreationController(GroupService groupService, ControllerService controllerService, ValidationService validationService) {
this.groupService = groupService;
this.controllerService = controllerService;
this.validationService = validationService;
}
@RolesAllowed({"ROLE_orga", "ROLE_actuator"})
@GetMapping("/createOrga")
public String createGroupAsOrga(KeycloakAuthenticationToken token,
Model model) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
model.addAttribute("account", account);
model.addAttribute("lectures", groupService.getAllLecturesWithVisibilityPublic());
return "createOrga";
}
@RolesAllowed({"ROLE_orga", "ROLE_actuator"})
@PostMapping("/createOrga")
@CacheEvict(value = "groups", allEntries = true)
public String postCrateGroupAsOrga(KeycloakAuthenticationToken token,
@RequestParam("title") String title,
@RequestParam("description") String description,
@RequestParam(value = "visibility", required = false) Boolean visibility,
@RequestParam(value = "lecture", required = false) Boolean lecture,
@RequestParam("userMaximum") Long userMaximum,
@RequestParam(value = "maxInfiniteUsers", required = false) Boolean maxInfiniteUsers,
@RequestParam(value = "parent", required = false) String parent,
@RequestParam(value = "file", required = false) MultipartFile file) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
UUID parentUUID = controllerService.getUUID(parent);
validationService.checkFields(description, title, userMaximum, maxInfiniteUsers);
controllerService.createGroupAsOrga(account,
title,
description,
visibility,
lecture,
maxInfiniteUsers,
userMaximum,
parentUUID,
file);
return "redirect:/gruppen2";
}
@RolesAllowed("ROLE_studentin")
@GetMapping("/createStudent")
public String createGroupAsStudent(KeycloakAuthenticationToken token,
Model model) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
model.addAttribute("account", account);
model.addAttribute("lectures", groupService.getAllLecturesWithVisibilityPublic());
return "createStudent";
}
@RolesAllowed("ROLE_studentin")
@PostMapping("/createStudent")
@CacheEvict(value = "groups", allEntries = true)
public String postCreateGroupAsStudent(KeycloakAuthenticationToken token,
@RequestParam("title") String title,
@RequestParam("description") String description,
@RequestParam("userMaximum") Long userMaximum,
@RequestParam(value = "visibility", required = false) Boolean visibility,
@RequestParam(value = "maxInfiniteUsers", required = false) Boolean maxInfiniteUsers,
@RequestParam(value = "parent", required = false) String parent) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
UUID parentUUID = controllerService.getUUID(parent);
validationService.checkFields(description, title, userMaximum, maxInfiniteUsers);
controllerService.createGroup(account,
title,
description,
visibility,
null,
maxInfiniteUsers,
userMaximum,
parentUUID);
return "redirect:/gruppen2";
}
}

View File

@ -1,281 +0,0 @@
package mops.gruppen2.controller;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.Role;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.service.ControllerService;
import mops.gruppen2.service.InviteService;
import mops.gruppen2.service.KeyCloakService;
import mops.gruppen2.service.UserService;
import mops.gruppen2.service.ValidationService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.annotation.SessionScope;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
@Controller
@SessionScope
@RequestMapping("/gruppen2")
public class GroupDetailsController {
private final ControllerService controllerService;
private final UserService userService;
private final ValidationService validationService;
private final InviteService inviteService;
public GroupDetailsController(ControllerService controllerService, UserService userService, ValidationService validationService, InviteService inviteService) {
this.controllerService = controllerService;
this.userService = userService;
this.validationService = validationService;
this.inviteService = inviteService;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/details/{id}")
public String showGroupDetails(KeycloakAuthenticationToken token,
Model model,
HttpServletRequest request,
@PathVariable("id") String groupId) {
Group group = userService.getGroupById(UUID.fromString(groupId));
Account account = KeyCloakService.createAccountFromPrincipal(token);
User user = new User(account);
UUID parentId = group.getParent();
String actualURL = request.getRequestURL().toString();
String serverURL = actualURL.substring(0, actualURL.indexOf("gruppen2/"));
Group parent = controllerService.getParent(parentId);
validationService.throwIfGroupNotExisting(group.getTitle());
model.addAttribute("account", account);
if (!validationService.checkIfUserInGroup(group, user)) {
validationService.throwIfNoAccessToPrivate(group, user);
model.addAttribute("group", group);
model.addAttribute("parentId", parentId);
model.addAttribute("parent", parent);
return "detailsNoMember";
}
model.addAttribute("parentId", parentId);
model.addAttribute("parent", parent);
model.addAttribute("group", group);
model.addAttribute("roles", group.getRoles());
model.addAttribute("user", user);
model.addAttribute("admin", Role.ADMIN);
model.addAttribute("public", Visibility.PUBLIC);
model.addAttribute("private", Visibility.PRIVATE);
if (validationService.checkIfAdmin(group, user)) {
model.addAttribute("link", serverURL + "gruppen2/acceptinvite/" + inviteService.getLinkByGroupId(group.getId()));
}
return "detailsMember";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/details/changeMetadata/{id}")
public String changeMetadata(KeycloakAuthenticationToken token,
Model model,
@PathVariable("id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
User user = new User(account);
Group group = userService.getGroupById(UUID.fromString(groupId));
validationService.throwIfNoAdmin(group, user);
model.addAttribute("account", account);
model.addAttribute("title", group.getTitle());
model.addAttribute("description", group.getDescription());
model.addAttribute("admin", Role.ADMIN);
model.addAttribute("roles", group.getRoles());
model.addAttribute("groupId", group.getId());
model.addAttribute("user", user);
return "changeMetadata";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/details/changeMetadata")
@CacheEvict(value = "groups", allEntries = true)
public String postChangeMetadata(KeycloakAuthenticationToken token,
@RequestParam("title") String title,
@RequestParam("description") String description,
@RequestParam("groupId") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
User user = new User(account);
Group group = userService.getGroupById(UUID.fromString(groupId));
validationService.throwIfNoAdmin(group, user);
validationService.checkFields(title, description);
controllerService.changeMetaData(account, group, title, description);
return "redirect:/gruppen2/details/" + groupId;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/details/members/{id}")
public String editMembers(KeycloakAuthenticationToken token,
Model model,
@PathVariable("id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
Group group = userService.getGroupById(UUID.fromString(groupId));
User user = new User(account);
validationService.throwIfNoAdmin(group, user);
model.addAttribute("account", account);
model.addAttribute("members", group.getMembers());
model.addAttribute("group", group);
model.addAttribute("admin", Role.ADMIN);
return "editMembers";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/details/members/changeRole")
@CacheEvict(value = "groups", allEntries = true)
public String changeRole(KeycloakAuthenticationToken token,
@RequestParam("group_id") String groupId,
@RequestParam("user_id") String userId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
Group group = userService.getGroupById(UUID.fromString(groupId));
User principle = new User(account);
User user = new User(userId, "", "", "");
validationService.throwIfNoAdmin(group, principle);
//TODO: checkIfAdmin checkt nicht, dass die rolle geändert wurde. oder die rolle wird nicht geändert
controllerService.changeRole(account, user, group);
if (!validationService.checkIfAdmin(group, principle)) {
return "redirect:/gruppen2/details/" + groupId;
}
return "redirect:/gruppen2/details/members/" + groupId;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/details/members/changeMaximum")
@CacheEvict(value = "groups", allEntries = true)
public String changeMaxSize(KeycloakAuthenticationToken token,
@RequestParam("maximum") Long maximum,
@RequestParam("group_id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
Group group = userService.getGroupById(UUID.fromString(groupId));
validationService.throwIfNewMaximumIsValid(maximum, group);
controllerService.updateMaxUser(account, UUID.fromString(groupId), maximum);
return "redirect:/gruppen2/details/members/" + groupId;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/details/members/deleteUser")
@CacheEvict(value = "groups", allEntries = true)
public String deleteUser(KeycloakAuthenticationToken token,
@RequestParam("group_id") String groupId,
@RequestParam("user_id") String userId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
User principle = new User(account);
User user = new User(userId, "", "", "");
Group group = userService.getGroupById(UUID.fromString(groupId));
validationService.throwIfNoAdmin(group, principle);
controllerService.deleteUser(account, user, group);
if (!validationService.checkIfUserInGroup(group, principle)) {
return "redirect:/gruppen2";
}
return "redirect:/gruppen2/details/members/" + groupId;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/detailsBeitreten")
@CacheEvict(value = "groups", allEntries = true)
public String joinGroup(KeycloakAuthenticationToken token,
Model model,
@RequestParam("id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
User user = new User(account);
Group group = userService.getGroupById(UUID.fromString(groupId));
validationService.throwIfUserAlreadyInGroup(group, user);
validationService.throwIfGroupFull(group);
controllerService.addUser(account, UUID.fromString(groupId));
model.addAttribute("account", account);
return "redirect:/gruppen2";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/leaveGroup")
@CacheEvict(value = "groups", allEntries = true)
public String leaveGroup(KeycloakAuthenticationToken token,
@RequestParam("group_id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
User user = new User(account);
Group group = userService.getGroupById(UUID.fromString(groupId));
controllerService.deleteUser(account, user, group);
return "redirect:/gruppen2";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/deleteGroup")
@CacheEvict(value = "groups", allEntries = true)
public String deleteGroup(KeycloakAuthenticationToken token,
@RequestParam("group_id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
User user = new User(account);
Group group = userService.getGroupById(UUID.fromString(groupId));
validationService.throwIfNoAdmin(group, user);
controllerService.deleteGroupEvent(user.getId(), UUID.fromString(groupId));
return "redirect:/gruppen2";
}
@RolesAllowed({"ROLE_orga", "ROLE_actuator"})
@PostMapping("/details/members/addUsersFromCsv")
@CacheEvict(value = "groups", allEntries = true)
public String addUsersFromCsv(KeycloakAuthenticationToken token,
@RequestParam("group_id") String groupId,
@RequestParam(value = "file", required = false) MultipartFile file) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
controllerService.addUsersFromCsv(account, file, groupId);
return "redirect:/gruppen2/details/members/" + groupId;
}
}

View File

@ -1,123 +0,0 @@
package mops.gruppen2.controller;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.Visibility;
import mops.gruppen2.service.ControllerService;
import mops.gruppen2.service.InviteService;
import mops.gruppen2.service.KeyCloakService;
import mops.gruppen2.service.UserService;
import mops.gruppen2.service.ValidationService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.annotation.SessionScope;
import javax.annotation.security.RolesAllowed;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Controller
@SessionScope
@RequestMapping("/gruppen2")
public class SearchAndInviteController {
private final ValidationService validationService;
private final InviteService inviteService;
private final UserService userService;
private final ControllerService controllerService;
public SearchAndInviteController(ValidationService validationService, InviteService inviteService, UserService userService, ControllerService controllerService) {
this.validationService = validationService;
this.inviteService = inviteService;
this.userService = userService;
this.controllerService = controllerService;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/findGroup")
public String findGroup(KeycloakAuthenticationToken token,
Model model,
@RequestParam(value = "suchbegriff", required = false) String search) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
List<Group> groups = new ArrayList<>();
groups = validationService.checkSearch(search, groups, account);
model.addAttribute("account", account);
model.addAttribute("gruppen", groups);
model.addAttribute("inviteService", inviteService);
return "search";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/detailsSearch")
public String showGroupDetailsNoMember(KeycloakAuthenticationToken token,
Model model,
@RequestParam("id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
Group group = userService.getGroupById(UUID.fromString(groupId));
UUID parentId = group.getParent();
Group parent = controllerService.getParent(parentId);
User user = new User(account);
model.addAttribute("account", account);
if (validationService.checkIfUserInGroup(group, user)) {
return "redirect:/gruppen2/details/" + groupId;
}
model.addAttribute("group", group);
model.addAttribute("parentId", parentId);
model.addAttribute("parent", parent);
return "detailsNoMember";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@GetMapping("/acceptinvite/{link}")
public String acceptInvite(KeycloakAuthenticationToken token,
Model model,
@PathVariable("link") String link) {
Group group = userService.getGroupById(inviteService.getGroupIdFromLink(link));
validationService.throwIfGroupNotExisting(group.getTitle());
model.addAttribute("account", KeyCloakService.createAccountFromPrincipal(token));
model.addAttribute("group", group);
if (group.getVisibility() == Visibility.PUBLIC) {
return "redirect:/gruppen2/details/" + group.getId();
}
return "joinprivate";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"})
@PostMapping("/acceptinvite")
@CacheEvict(value = "groups", allEntries = true)
public String postAcceptInvite(KeycloakAuthenticationToken token,
@RequestParam("id") String groupId) {
Account account = KeyCloakService.createAccountFromPrincipal(token);
User user = new User(account);
Group group = userService.getGroupById(UUID.fromString(groupId));
validationService.throwIfUserAlreadyInGroup(group, user);
validationService.throwIfGroupFull(group);
controllerService.addUser(account, UUID.fromString(groupId));
return "redirect:/gruppen2/details/" + groupId;
}
}

View File

@ -1,10 +1,14 @@
package mops.gruppen2.domain; package mops.gruppen2.domain;
import lombok.AllArgsConstructor;
import lombok.Value; import lombok.Value;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import java.util.Set; import java.util.Set;
@Value @Value
@AllArgsConstructor
public class Account { public class Account {
String name; //user_id String name; //user_id
@ -13,4 +17,14 @@ public class Account {
String givenname; String givenname;
String familyname; String familyname;
Set<String> roles; Set<String> roles;
public Account(KeycloakAuthenticationToken token) {
KeycloakPrincipal principal = (KeycloakPrincipal) token.getPrincipal();
name = principal.getName();
email = principal.getKeycloakSecurityContext().getIdToken().getEmail();
image = null;
givenname = principal.getKeycloakSecurityContext().getIdToken().getGivenName();
familyname = principal.getKeycloakSecurityContext().getIdToken().getFamilyName();
roles = token.getAccount().getRoles();
}
} }

View File

@ -1,35 +0,0 @@
package mops.gruppen2.domain;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Repräsentiert den aggregierten Zustand einer Gruppe.
*/
@Getter
@Setter
public class Group {
//TODO: List to Hashmap
private final List<User> members;
private final Map<String, Role> roles;
private UUID id;
private String title;
private String description;
private Long userMaximum;
private GroupType type;
private Visibility visibility;
private UUID parent;
public Group() {
members = new ArrayList<>();
roles = new HashMap<>();
}
}

View File

@ -1,6 +0,0 @@
package mops.gruppen2.domain;
public enum GroupType {
SIMPLE,
LECTURE
}

View File

@ -1,6 +0,0 @@
package mops.gruppen2.domain;
public enum Role {
ADMIN,
MEMBER
}

View File

@ -1,25 +0,0 @@
package mops.gruppen2.domain;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(exclude = {"givenname", "familyname", "email"})
public class User {
private String id;
private String givenname;
private String familyname;
private String email;
public User(Account account) {
id = account.getName();
givenname = account.getGivenname();
familyname = account.getFamilyname();
email = account.getEmail();
}
}

View File

@ -1,6 +0,0 @@
package mops.gruppen2.domain;
public enum Visibility {
PUBLIC,
PRIVATE
}

View File

@ -1,17 +0,0 @@
package mops.gruppen2.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
@Table("invite")
@Getter
@AllArgsConstructor
public class InviteLinkDTO {
@Id
Long invite_id;
String group_id;
String invite_link;
}

View File

@ -0,0 +1,57 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.exception.UserExistsException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.infrastructure.GroupCache;
import java.util.UUID;
/**
* Fügt einen einzelnen Nutzer einer Gruppe hinzu.
*/
@Log4j2
@Value
@AllArgsConstructor
public class AddMemberEvent extends Event {
@JsonProperty("user")
User user;
public AddMemberEvent(UUID groupId, String exec, String target, User user) throws IdMismatchException {
super(groupId, exec, target);
this.user = user;
if (!target.equals(user.getId())) {
throw new IdMismatchException("Der User passt nicht zur angegebenen userid.");
}
}
@Override
protected void updateCache(GroupCache cache, Group group) {
cache.usersPut(target, group);
}
@Override
protected void applyEvent(Group group) throws UserExistsException, GroupFullException {
group.addMember(target, user);
log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers());
}
@Override
public String format() {
return "Benutzer hinzugefügt: " + target + ".";
}
@Override
public String type() {
return EventType.ADDMEMBER.toString();
}
}

View File

@ -1,47 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.Role;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.UserAlreadyExistsException;
import java.util.UUID;
/**
* Fügt einen einzelnen Nutzer einer Gruppe hinzu.
*/
@Getter
@NoArgsConstructor // For Jackson
public class AddUserEvent extends Event {
private String givenname;
private String familyname;
private String email;
public AddUserEvent(UUID groupId, String userId, String givenname, String familyname, String email) {
super(groupId, userId);
this.givenname = givenname;
this.familyname = familyname;
this.email = email;
}
@Override
protected void applyEvent(Group group) throws EventException {
User user = new User(userId, givenname, familyname, email);
if (group.getMembers().contains(user)) {
throw new UserAlreadyExistsException(getClass().toString());
}
if (group.getMembers().size() >= group.getUserMaximum()) {
throw new GroupFullException(getClass().toString());
}
group.getMembers().add(user);
group.getRoles().put(userId, Role.MEMBER);
}
}

View File

@ -1,36 +1,56 @@
package mops.gruppen2.domain.event; package mops.gruppen2.domain.event;
import lombok.Getter; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.NoArgsConstructor; import lombok.AllArgsConstructor;
import mops.gruppen2.domain.Group; import lombok.Value;
import mops.gruppen2.domain.GroupType; import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.Visibility; import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.GroupCache;
import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
@Getter @Log4j2
@NoArgsConstructor // For Jackson @Value
@AllArgsConstructor// Value generiert den allArgsConstrucot nur, wenn keiner explizit angegeben ist
public class CreateGroupEvent extends Event { public class CreateGroupEvent extends Event {
private Visibility groupVisibility; @JsonProperty("date")
private UUID groupParent; LocalDateTime date;
private GroupType groupType;
private Long groupUserMaximum;
public CreateGroupEvent(UUID groupId, String userId, UUID parent, GroupType type, Visibility visibility, Long userMaximum) { public CreateGroupEvent(UUID groupId, String exec, LocalDateTime date) {
super(groupId, userId); super(groupId, exec, null);
groupParent = parent; this.date = date;
groupType = type;
groupVisibility = visibility;
groupUserMaximum = userMaximum;
} }
@Override @Override
protected void applyEvent(Group group) { protected void updateCache(GroupCache cache, Group group) {
group.setId(groupId); cache.groupsPut(groupid, group);
group.setParent(groupParent); cache.linksPut(group.getLink(), group);
group.setType(groupType); }
group.setVisibility(groupVisibility);
group.setUserMaximum(groupUserMaximum); @Override
protected void applyEvent(Group group) throws BadArgumentException {
group.setId(groupid);
group.setCreator(exec);
group.setCreationDate(date);
log.trace("\t\t\t\t\tNeue Gruppe: {}", group.toString());
}
@Override
public String format() {
return "Gruppe erstellt.";
}
@Override
public String type() {
return EventType.CREATEGROUP.toString();
}
@Override
public String toString() {
return "(" + version + "," + groupid + "," + date + ")";
} }
} }

View File

@ -1,28 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mops.gruppen2.domain.Group;
import java.util.UUID;
@Getter
@NoArgsConstructor // For Jackson
public class DeleteGroupEvent extends Event {
public DeleteGroupEvent(UUID groupId, String userId) {
super(groupId, userId);
}
@Override
protected void applyEvent(Group group) {
group.getRoles().clear();
group.getMembers().clear();
group.setTitle(null);
group.setDescription(null);
group.setVisibility(null);
group.setType(null);
group.setParent(null);
group.setUserMaximum(0L);
}
}

View File

@ -1,34 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.User;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import java.util.UUID;
/**
* Entfernt ein einzelnes Mitglied einer Gruppe.
*/
@Getter
@NoArgsConstructor // For Jackson
public class DeleteUserEvent extends Event {
public DeleteUserEvent(UUID groupId, String userId) {
super(groupId, userId);
}
@Override
protected void applyEvent(Group group) throws EventException {
for (User user : group.getMembers()) {
if (user.getId().equals(this.userId)) {
group.getMembers().remove(user);
group.getRoles().remove(user.getId());
return;
}
}
throw new UserNotFoundException(this.getClass().toString());
}
}

View File

@ -0,0 +1,42 @@
package mops.gruppen2.domain.event;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.GroupCache;
import java.util.UUID;
@Log4j2
@Value
@AllArgsConstructor
public class DestroyGroupEvent extends Event {
public DestroyGroupEvent(UUID groupId, String exec) {
super(groupId, exec, null);
}
@Override
protected void updateCache(GroupCache cache, Group group) {
cache.groupsRemove(groupid, group);
}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.destroy(exec);
log.trace("\t\t\t\t\tGelöschte Gruppe: {}", group.toString());
}
@Override
public String format() {
return "Gruppe gelöscht.";
}
@Override
public String type() {
return EventType.DESTROYGROUP.toString();
}
}

View File

@ -1,51 +1,119 @@
package mops.gruppen2.domain.event; package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import mops.gruppen2.domain.Group; import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.EventException; import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.GroupIdMismatchException; import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.GroupCache;
import java.time.LocalDateTime;
import java.util.UUID; import java.util.UUID;
import static com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
@JsonTypeInfo( @Log4j2
use = JsonTypeInfo.Id.NAME, @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "class")
property = "type" @JsonSubTypes({@Type(value = AddMemberEvent.class, name = "ADDMEMBER"),
) @Type(value = CreateGroupEvent.class, name = "CREATEGROUP"),
@JsonSubTypes({ @Type(value = DestroyGroupEvent.class, name = "DESTROYGROUP"),
@JsonSubTypes.Type(value = AddUserEvent.class, name = "AddUserEvent"), @Type(value = KickMemberEvent.class, name = "KICKMEMBER"),
@JsonSubTypes.Type(value = CreateGroupEvent.class, name = "CreateGroupEvent"), @Type(value = SetDescriptionEvent.class, name = "SETDESCRIPTION"),
@JsonSubTypes.Type(value = DeleteUserEvent.class, name = "DeleteUserEvent"), @Type(value = SetInviteLinkEvent.class, name = "SETLINK"),
@JsonSubTypes.Type(value = UpdateGroupDescriptionEvent.class, name = "UpdateGroupDescriptionEvent"), @Type(value = SetLimitEvent.class, name = "SETLIMIT"),
@JsonSubTypes.Type(value = UpdateGroupTitleEvent.class, name = "UpdateGroupTitleEvent"), @Type(value = SetParentEvent.class, name = "SETPARENT"),
@JsonSubTypes.Type(value = UpdateRoleEvent.class, name = "UpdateRoleEvent"), @Type(value = SetTitleEvent.class, name = "SETTITLE"),
@JsonSubTypes.Type(value = DeleteGroupEvent.class, name = "DeleteGroupEvent"), @Type(value = SetTypeEvent.class, name = "SETTYPE"),
@JsonSubTypes.Type(value = UpdateUserMaxEvent.class, name = "UpdateUserMaxEvent") @Type(value = UpdateRoleEvent.class, name = "UPDATEROLE")})
})
@Getter @Getter
@NoArgsConstructor @NoArgsConstructor // Lombok needs a default constructor in the base class
@AllArgsConstructor
public abstract class Event { public abstract class Event {
protected UUID groupId; @JsonProperty("groupid")
protected String userId; protected UUID groupid;
public void apply(Group group) throws EventException { @JsonProperty("version")
checkGroupIdMatch(group.getId()); protected long version; // Group-Version
applyEvent(group);
@JsonProperty("exec")
protected String exec;
@JsonProperty("target")
protected String target;
@JsonProperty("date")
protected LocalDateTime date;
//TODO: Eigentlich sollte die Gruppe aus dem Cache genommen werden, nicht übergeben
public Event(UUID groupid, String exec, String target) {
this.groupid = groupid;
this.exec = exec;
this.target = target;
} }
private void checkGroupIdMatch(UUID groupId) { public void init(long version) {
if (groupId == null || this.groupId.equals(groupId)) { if (this.version != 0) {
throw new BadArgumentException("Event wurde schon initialisiert. (" + type() + ")");
}
date = LocalDateTime.now();
log.trace("Event wurde initialisiert. (" + type() + "," + version + ")");
this.version = version;
}
public void apply(Group group, GroupCache cache) throws EventException {
log.trace("Event wird angewendet:\t{}", this);
if (version == 0) {
throw new BadArgumentException("Event wurde nicht initialisiert.");
}
checkGroupIdMatch(group.getId());
group.updateVersion(version);
applyEvent(group);
updateCache(cache, group); // Update erst nachdem apply keine exception geworfen hat
}
public void apply(Group group) throws EventException {
log.trace("Event wird angewendet:\t{}", this);
if (version == 0) {
throw new BadArgumentException("Event wurde nicht initialisiert.");
}
checkGroupIdMatch(group.getId());
group.updateVersion(version);
applyEvent(group);
}
private void checkGroupIdMatch(UUID groupid) throws IdMismatchException {
// CreateGroupEvents müssen die Id erst initialisieren
if (this instanceof CreateGroupEvent) {
return; return;
} }
throw new GroupIdMismatchException(getClass().toString()); if (!this.groupid.equals(groupid)) {
throw new IdMismatchException("Das Event gehört zu einer anderen Gruppe");
}
} }
protected abstract void updateCache(GroupCache cache, Group group);
protected abstract void applyEvent(Group group) throws EventException; protected abstract void applyEvent(Group group) throws EventException;
@JsonIgnore
public abstract String format();
@JsonIgnore
public abstract String type();
} }

View File

@ -0,0 +1,15 @@
package mops.gruppen2.domain.event;
public enum EventType {
ADDMEMBER,
CREATEGROUP,
DESTROYGROUP,
KICKMEMBER,
SETDESCRIPTION,
SETLINK,
SETLIMIT,
SETPARENT,
SETTITLE,
SETTYPE,
UPDATEROLE
}

View File

@ -0,0 +1,46 @@
package mops.gruppen2.domain.event;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.GroupCache;
import java.util.UUID;
/**
* Entfernt ein einzelnes Mitglied einer Gruppe.
*/
@Log4j2
@Value
@AllArgsConstructor
public class KickMemberEvent extends Event {
public KickMemberEvent(UUID groupId, String exec, String target) {
super(groupId, exec, target);
}
@Override
protected void updateCache(GroupCache cache, Group group) {
cache.usersRemove(target, group);
}
@Override
protected void applyEvent(Group group) throws UserNotFoundException, LastAdminException {
group.kickMember(target);
log.trace("\t\t\t\t\tNeue Members: {}", group.getMembers());
}
@Override
public String format() {
return "Mitglied entfernt: " + target + ".";
}
@Override
public String type() {
return EventType.KICKMEMBER.toString();
}
}

View File

@ -0,0 +1,50 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.infrastructure.GroupCache;
import javax.validation.Valid;
import java.util.UUID;
/**
* Ändert nur die Gruppenbeschreibung.
*/
@Log4j2
@Value
@AllArgsConstructor
public class SetDescriptionEvent extends Event {
@JsonProperty("desc")
Description description;
public SetDescriptionEvent(UUID groupId, String exec, @Valid Description description) {
super(groupId, exec, null);
this.description = description;
}
@Override
protected void updateCache(GroupCache cache, Group group) {}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.setDescription(exec, description);
log.trace("\t\t\t\t\tNeue Beschreibung: {}", group.getDescription());
}
@Override
public String format() {
return "Beschreibung gesetzt: " + description + ".";
}
@Override
public String type() {
return EventType.SETDESCRIPTION.toString();
}
}

View File

@ -0,0 +1,50 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Link;
import mops.gruppen2.infrastructure.GroupCache;
import javax.validation.Valid;
import java.util.UUID;
@Log4j2
@Value
@AllArgsConstructor
public class SetInviteLinkEvent extends Event {
@JsonProperty("link")
Link link;
public SetInviteLinkEvent(UUID groupId, String exec, @Valid Link link) {
super(groupId, exec, null);
this.link = link;
}
@Override
protected void updateCache(GroupCache cache, Group group) {
cache.linksRemove(group.getLink());
cache.linksPut(link.getValue(), group);
}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.setLink(exec, link);
log.trace("\t\t\t\t\tNeuer Link: {}", group.getLink());
}
@Override
public String format() {
return "Einladungslink gesetzt: " + link + ".";
}
@Override
public String type() {
return EventType.SETLINK.toString();
}
}

View File

@ -0,0 +1,48 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.infrastructure.GroupCache;
import javax.validation.Valid;
import java.util.UUID;
@Log4j2
@Value
@AllArgsConstructor
public class SetLimitEvent extends Event {
@JsonProperty("limit")
Limit limit;
public SetLimitEvent(UUID groupId, String exec, @Valid Limit limit) {
super(groupId, exec, null);
this.limit = limit;
}
@Override
protected void updateCache(GroupCache cache, Group group) {}
@Override
protected void applyEvent(Group group) throws BadArgumentException, NoAccessException {
group.setLimit(exec, limit);
log.trace("\t\t\t\t\tNeues UserLimit: {}", group.getLimit());
}
@Override
public String format() {
return "Benutzerlimit gesetzt: " + limit + ".";
}
@Override
public String type() {
return EventType.SETLIMIT.toString();
}
}

View File

@ -0,0 +1,48 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import mops.gruppen2.infrastructure.GroupCache;
import javax.validation.Valid;
import java.util.UUID;
@Log4j2
@Value
@AllArgsConstructor
public class SetParentEvent extends Event {
@JsonProperty("parent")
Parent parent;
public SetParentEvent(UUID groupId, String exec, @Valid Parent parent) {
super(groupId, exec, null);
this.parent = parent;
}
@Override
protected void updateCache(GroupCache cache, Group group) {}
@Override
protected void applyEvent(Group group) throws NoAccessException, BadArgumentException {
group.setParent(exec, parent);
log.trace("\t\t\t\t\tNeues Parent: {}", group.getParent());
}
@Override
public String format() {
return "Veranstaltungszugehörigkeit gesetzt: " + parent.getValue() + ".";
}
@Override
public String type() {
return EventType.SETPARENT.toString();
}
}

View File

@ -0,0 +1,51 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.wrapper.Title;
import mops.gruppen2.infrastructure.GroupCache;
import javax.validation.Valid;
import java.util.UUID;
/**
* Ändert nur den Gruppentitel.
*/
@Log4j2
@Value
@AllArgsConstructor
public class SetTitleEvent extends Event {
@JsonProperty("title")
Title title;
public SetTitleEvent(UUID groupId, String exec, @Valid Title title) {
super(groupId, exec, null);
this.title = title;
}
@Override
protected void updateCache(GroupCache cache, Group group) {}
@Override
protected void applyEvent(Group group) throws NoAccessException {
group.setTitle(exec, title);
log.trace("\t\t\t\t\tNeuer Titel: {}", group.getTitle());
}
@Override
public String format() {
return "Titel gesetzt: " + title + ".";
}
@Override
public String type() {
return EventType.SETTITLE.toString();
}
}

View File

@ -0,0 +1,57 @@
package mops.gruppen2.domain.event;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.experimental.NonFinal;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.infrastructure.GroupCache;
import javax.validation.Valid;
import java.util.UUID;
@Log4j2
@Value
@AllArgsConstructor
public class SetTypeEvent extends Event {
@JsonProperty("type")
Type type;
//TODO: blöder hack, das soll eigentlich anders gehen
// Problem ist, dass die Gruppe vor dem Cache verändert wird, also kann der cache den alten Typ
// nicht mehr aus der Gruppe holen
@NonFinal
Type oldType;
public SetTypeEvent(UUID groupId, String exec, @Valid Type type) {
super(groupId, exec, null);
this.type = type;
}
@Override
protected void updateCache(GroupCache cache, Group group) {
cache.typesRemove(oldType, group);
cache.typesPut(type, group);
}
@Override
protected void applyEvent(Group group) throws EventException {
oldType = group.getType();
group.setType(exec, type);
}
@Override
public String format() {
return "Gruppentype gesetzt: " + type + ".";
}
@Override
public String type() {
return EventType.SETTYPE.toString();
}
}

View File

@ -1,32 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.exception.BadParameterException;
import java.util.UUID;
/**
* Ändert nur die Gruppenbeschreibung.
*/
@Getter
@NoArgsConstructor // For Jackson
public class UpdateGroupDescriptionEvent extends Event {
private String newGroupDescription;
public UpdateGroupDescriptionEvent(UUID groupId, String userId, String newGroupDescription) {
super(groupId, userId);
this.newGroupDescription = newGroupDescription.trim();
}
@Override
protected void applyEvent(Group group) {
if (newGroupDescription.isEmpty()) {
throw new BadParameterException("Die Beschreibung ist leer.");
}
group.setDescription(newGroupDescription);
}
}

View File

@ -1,33 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.exception.BadParameterException;
import java.util.UUID;
/**
* Ändert nur den Gruppentitel.
*/
@Getter
@NoArgsConstructor // For Jackson
public class UpdateGroupTitleEvent extends Event {
private String newGroupTitle;
public UpdateGroupTitleEvent(UUID groupId, String userId, String newGroupTitle) {
super(groupId, userId);
this.newGroupTitle = newGroupTitle.trim();
}
@Override
protected void applyEvent(Group group) {
if (newGroupTitle.isEmpty()) {
throw new BadParameterException("Der Titel ist leer.");
}
group.setTitle(newGroupTitle);
}
}

View File

@ -1,35 +1,51 @@
package mops.gruppen2.domain.event; package mops.gruppen2.domain.event;
import lombok.Getter; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.NoArgsConstructor; import lombok.AllArgsConstructor;
import mops.gruppen2.domain.Group; import lombok.Value;
import mops.gruppen2.domain.Role; import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.UserNotFoundException; import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Role;
import mops.gruppen2.infrastructure.GroupCache;
import java.util.UUID; import java.util.UUID;
/** /**
* Aktualisiert die Gruppenrolle eines Teilnehmers. * Aktualisiert die Gruppenrolle eines Teilnehmers.
*/ */
@Getter @Log4j2
@NoArgsConstructor // For Jackson @Value
@AllArgsConstructor
public class UpdateRoleEvent extends Event { public class UpdateRoleEvent extends Event {
private Role newRole; @JsonProperty("role")
Role role;
public UpdateRoleEvent(UUID groupId, String userId, Role newRole) { public UpdateRoleEvent(UUID groupId, String exec, String target, Role role) {
super(groupId, userId); super(groupId, exec, target);
this.newRole = newRole; this.role = role;
} }
@Override @Override
protected void applyEvent(Group group) throws UserNotFoundException { protected void updateCache(GroupCache cache, Group group) {}
if (group.getRoles().containsKey(userId)) {
group.getRoles().put(userId, newRole);
return;
}
throw new UserNotFoundException(getClass().toString()); @Override
protected void applyEvent(Group group) throws UserNotFoundException, LastAdminException {
group.memberPutRole(target, role);
log.trace("\t\t\t\t\tNeue Admin: {}", group.getAdmins());
}
@Override
public String format() {
return "Mitgliedsrolle gesetzt: " + target + ": " + role + ".";
}
@Override
public String type() {
return EventType.UPDATEROLE.toString();
} }
} }

View File

@ -1,30 +0,0 @@
package mops.gruppen2.domain.event;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mops.gruppen2.domain.Group;
import mops.gruppen2.domain.exception.BadParameterException;
import mops.gruppen2.domain.exception.EventException;
import java.util.UUID;
@Getter
@NoArgsConstructor
public class UpdateUserMaxEvent extends Event {
private Long userMaximum;
public UpdateUserMaxEvent(UUID groupId, String userId, Long userMaximum) {
super(groupId, userId);
this.userMaximum = userMaximum;
}
@Override
protected void applyEvent(Group group) throws EventException {
if (userMaximum <= 0 || userMaximum < group.getMembers().size()) {
throw new BadParameterException("Usermaximum zu klein.");
}
group.setUserMaximum(userMaximum);
}
}

View File

@ -0,0 +1,12 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class BadArgumentException extends EventException {
private static final long serialVersionUID = -6757742013238625595L;
public BadArgumentException(String info) {
super(HttpStatus.BAD_REQUEST, "Fehlerhafter Parameter.", info);
}
}

View File

@ -1,10 +0,0 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class BadParameterException extends EventException {
public BadParameterException(String info) {
super(HttpStatus.BAD_REQUEST, "Fehlerhafter Parameter angegeben!", info);
}
}

View File

@ -0,0 +1,13 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class BadPayloadException extends EventException {
private static final long serialVersionUID = -3978242017847155629L;
public BadPayloadException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Payload konnte nicht übersetzt werden.", info);
}
}

View File

@ -5,8 +5,10 @@ import org.springframework.web.server.ResponseStatusException;
public class EventException extends ResponseStatusException { public class EventException extends ResponseStatusException {
private static final long serialVersionUID = 6784052016028094340L;
public EventException(HttpStatus status, String msg, String info) { public EventException(HttpStatus status, String msg, String info) {
super(status, msg + " (" + info + ")"); super(status, info.isBlank() ? "" : msg + " (" + info + ")");
} }
} }

View File

@ -4,8 +4,10 @@ import org.springframework.http.HttpStatus;
public class GroupFullException extends EventException { public class GroupFullException extends EventException {
private static final long serialVersionUID = -4011141160467668713L;
public GroupFullException(String info) { public GroupFullException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Die Gruppe hat die maximale Midgliederanzahl bereits erreicht!", info); super(HttpStatus.INTERNAL_SERVER_ERROR, "Gruppe hat maximale Teilnehmeranzahl bereits erreicht.", info);
} }
} }

View File

@ -1,10 +0,0 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class GroupIdMismatchException extends EventException {
public GroupIdMismatchException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Falsche Gruppe für Event.", info);
}
}

View File

@ -4,7 +4,10 @@ import org.springframework.http.HttpStatus;
public class GroupNotFoundException extends EventException { public class GroupNotFoundException extends EventException {
private static final long serialVersionUID = -4738218416842951106L;
public GroupNotFoundException(String info) { public GroupNotFoundException(String info) {
super(HttpStatus.NOT_FOUND, "Gruppe wurde nicht gefunden.", info); super(HttpStatus.NOT_FOUND, "Gruppe existiert nicht oder wurde gelöscht.", info);
} }
} }

View File

@ -0,0 +1,13 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class IdMismatchException extends EventException {
private static final long serialVersionUID = 7944077617758922089L;
public IdMismatchException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Ids stimmen nicht überein.", info);
}
}

View File

@ -4,7 +4,10 @@ import org.springframework.http.HttpStatus;
public class InvalidInviteException extends EventException { public class InvalidInviteException extends EventException {
private static final long serialVersionUID = 2643001101459427944L;
public InvalidInviteException(String info) { public InvalidInviteException(String info) {
super(HttpStatus.NOT_FOUND, "Der Einladungslink ist ungültig.", info); super(HttpStatus.NOT_FOUND, "Einladungslink ist ungültig oder Gruppe wurde gelöscht.", info);
} }
} }

View File

@ -0,0 +1,12 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class LastAdminException extends EventException {
private static final long serialVersionUID = 9059481382346544288L;
public LastAdminException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Gruppe braucht mindestens einen Admin.", info);
}
}

View File

@ -4,7 +4,10 @@ import org.springframework.http.HttpStatus;
public class NoAccessException extends EventException { public class NoAccessException extends EventException {
private static final long serialVersionUID = 1696988497122834654L;
public NoAccessException(String info) { public NoAccessException(String info) {
super(HttpStatus.FORBIDDEN, "Hier hast du leider keinen Zugriff!", info); super(HttpStatus.FORBIDDEN, "Kein Zugriff.", info);
} }
} }

View File

@ -1,10 +0,0 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class NoAdminAfterActionException extends EventException {
public NoAdminAfterActionException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Nach dieser Aktion hätte die Gruppe keinen Admin mehr", info);
}
}

View File

@ -4,7 +4,10 @@ import org.springframework.http.HttpStatus;
public class NoInviteExistException extends EventException { public class NoInviteExistException extends EventException {
private static final long serialVersionUID = -8092076461455840693L;
public NoInviteExistException(String info) { public NoInviteExistException(String info) {
super(HttpStatus.NOT_FOUND, "Für diese Gruppe existiert kein Link.", info); super(HttpStatus.NOT_FOUND, "Für diese Gruppe existiert kein Link.", info);
} }
} }

View File

@ -4,7 +4,10 @@ import org.springframework.http.HttpStatus;
public class PageNotFoundException extends EventException { public class PageNotFoundException extends EventException {
private static final long serialVersionUID = 2374509005158710104L;
public PageNotFoundException(String info) { public PageNotFoundException(String info) {
super(HttpStatus.NOT_FOUND, "Die Seite wurde nicht gefunden!", info); super(HttpStatus.NOT_FOUND, "Seite wurde nicht gefunden.", info);
} }
} }

View File

@ -1,10 +0,0 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class UserAlreadyExistsException extends EventException {
public UserAlreadyExistsException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "Der User existiert bereits.", info);
}
}

View File

@ -0,0 +1,13 @@
package mops.gruppen2.domain.exception;
import org.springframework.http.HttpStatus;
public class UserExistsException extends EventException {
private static final long serialVersionUID = -8150634358760194625L;
public UserExistsException(String info) {
super(HttpStatus.INTERNAL_SERVER_ERROR, "User existiert bereits.", info);
}
}

View File

@ -4,7 +4,10 @@ import org.springframework.http.HttpStatus;
public class UserNotFoundException extends EventException { public class UserNotFoundException extends EventException {
private static final long serialVersionUID = 8347442921199785291L;
public UserNotFoundException(String info) { public UserNotFoundException(String info) {
super(HttpStatus.NOT_FOUND, "Der User wurde nicht gefunden.", info); super(HttpStatus.NOT_FOUND, "User existiert nicht.", info);
} }
} }

View File

@ -4,7 +4,10 @@ import org.springframework.http.HttpStatus;
public class WrongFileException extends EventException { public class WrongFileException extends EventException {
private static final long serialVersionUID = -166192514348555116L;
public WrongFileException(String info) { public WrongFileException(String info) {
super(HttpStatus.BAD_REQUEST, "Die entsprechende Datei ist keine valide CSV-Datei!", info); super(HttpStatus.BAD_REQUEST, "Datei ist keine valide CSV-Datei.", info);
} }
} }

View File

@ -0,0 +1,357 @@
package mops.gruppen2.domain.model.group;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.exception.UserExistsException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.group.wrapper.Body;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Link;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import mops.gruppen2.domain.model.group.wrapper.Title;
import mops.gruppen2.domain.service.helper.CommonHelper;
import mops.gruppen2.domain.service.helper.SortHelper;
import mops.gruppen2.domain.service.helper.ValidationHelper;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Repräsentiert den aggregierten Zustand einer Gruppe.
*
* <p>
* Muss beim Start gesetzt werden: groupid, meta
*/
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Group {
// Metainformationen
@EqualsAndHashCode.Include
private UUID groupid;
@Getter
private Type type = Type.PRIVATE;
private Parent parent = Parent.EMPTY();
private Limit limit = Limit.DEFAULT(); // Add initial user
private Link link = Link.RANDOM();
private GroupMeta meta = GroupMeta.EMPTY();
//TODO: UI set + use for options
private final GroupOptions options = GroupOptions.DEFAULT();
// Inhalt
private Title title = Title.EMPTY();
private Description description = Description.EMPTY();
//TODO: Asciidoc description
private Body body;
// Integrationen
// Teilnehmer
private final Map<String, Membership> memberships = new HashMap<>();
// ####################################### Members ###########################################
public List<User> getMembers() {
return SortHelper.sortByMemberRole(new ArrayList<>(memberships.values())).stream()
.map(Membership::getUser)
.collect(Collectors.toList());
}
public List<User> getRegulars() {
return memberships.values().stream()
.map(Membership::getUser)
.filter(member -> isRegular(member.getId()))
.collect(Collectors.toList());
}
public List<User> getAdmins() {
return memberships.values().stream()
.map(Membership::getUser)
.filter(member -> isAdmin(member.getId()))
.collect(Collectors.toList());
}
public Role getRole(String userid) {
return memberships.get(userid).getRole();
}
public void addMember(String target, User user) throws UserExistsException, GroupFullException {
ValidationHelper.throwIfMember(this, target);
ValidationHelper.throwIfGroupFull(this);
memberships.put(target, new Membership(user, Role.REGULAR));
}
public void kickMember(String target) throws UserNotFoundException, LastAdminException {
ValidationHelper.throwIfNoMember(this, target);
ValidationHelper.throwIfLastAdmin(this, target);
memberships.remove(target);
}
public boolean memberHasRole(String target, Role role) {
ValidationHelper.throwIfNoMember(this, target);
return memberships.get(target).getRole() == role;
}
public void memberPutRole(String target, Role role) throws UserNotFoundException, LastAdminException {
ValidationHelper.throwIfNoMember(this, target);
if (role == Role.REGULAR) {
ValidationHelper.throwIfLastAdmin(this, target);
}
memberships.put(target, memberships.get(target).setRole(role));
}
public boolean isMember(String target) {
return memberships.containsKey(target);
}
public boolean isAdmin(String target) throws UserNotFoundException {
ValidationHelper.throwIfNoMember(this, target);
return memberships.get(target).getRole() == Role.ADMIN;
}
public boolean isRegular(String target) throws UserNotFoundException {
ValidationHelper.throwIfNoMember(this, target);
return memberships.get(target).getRole() == Role.REGULAR;
}
// ######################################### Getters #########################################
public UUID getId() {
return groupid;
}
public UUID getParent() {
return parent.getValue();
}
public long getLimit() {
return limit.getValue();
}
public String getTitle() {
return title.toString();
}
public String getDescription() {
return description.getValue();
}
public String getLink() {
return link.getValue();
}
public String creator() {
return meta.getCreator();
}
public long version() {
return meta.getVersion();
}
public LocalDateTime creationDate() {
return meta.getCreationDate();
}
public int size() {
return memberships.size();
}
public boolean isFull() {
return size() >= limit.getValue();
}
public boolean isEmpty() {
return size() == 0;
}
public boolean exists() {
return groupid != null && !CommonHelper.uuidIsEmpty(groupid);
}
public boolean isPublic() {
return type == Type.PUBLIC;
}
public boolean isPrivate() {
return type == Type.PRIVATE;
}
public boolean isLecture() {
return type == Type.LECTURE;
}
public boolean hasParent() {
return !parent.isEmpty();
}
public boolean hasMaterial() {
return options.isHasMaterialIntegration();
}
public boolean hasForums() {
return options.isHasForumsIntegration();
}
public boolean hasCalendar() {
return options.isHasTermineIntegration();
}
public boolean hasModules() {
return options.isHasModulesIntegration();
}
public boolean hasPortfolios() {
return options.isHasPortfolioIntegration();
}
// ######################################## Setters ##########################################
public void setId(UUID groupid) throws BadArgumentException {
if (this.groupid != null) {
throw new BadArgumentException("GruppenId bereits gesetzt.");
}
this.groupid = groupid;
}
public void setType(String exec, Type type) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.type = type;
}
public void setTitle(String exec, @Valid Title title) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.title = title;
}
public void setDescription(String exec, @Valid Description description) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
this.description = description;
}
public void setLimit(String exec, @Valid Limit limit) throws NoAccessException, BadArgumentException {
ValidationHelper.throwIfNoAdmin(this, exec);
if (size() > limit.getValue()) {
throw new BadArgumentException("Das Userlimit ist zu klein für die Gruppe.");
}
this.limit = limit;
}
public void setParent(String exec, @Valid Parent parent) throws NoAccessException, BadArgumentException {
ValidationHelper.throwIfNoAdmin(this, exec);
if (parent.getValue().equals(groupid)) {
throw new BadArgumentException("Die Gruppe kann nicht zu sich selbst gehören!");
}
this.parent = parent;
}
public void setLink(String exec, @Valid Link link) throws NoAccessException {
ValidationHelper.throwIfNoAdmin(this, exec);
if (link.getValue().equals(groupid.toString())) {
throw new BadArgumentException("Link kann nicht der GruppenID entsprechen.");
}
this.link = link;
}
public void updateVersion(long version) throws IdMismatchException {
meta = meta.setVersion(version);
}
public void setCreator(String target) throws BadArgumentException {
meta = meta.setCreator(target);
}
public void setCreationDate(LocalDateTime date) throws BadArgumentException {
meta = meta.setCreationDate(date);
}
// ######################################### Util ############################################
public void destroy(String userid) throws NoAccessException {
if (!isEmpty()) {
ValidationHelper.throwIfNoAdmin(this, userid);
}
groupid = null;
// Wenn man alles null setzt hat der cache mehr arbeit, weil dieser erst nach der löschung
// geupdated wird und sich link und mitgliedschaften selber heraussuchen muss
/*type = null;
parent = null;
limit = null;
link = null;
meta = null;
options = null;
title = null;
description = null;
body = null;
memberships = null;*/
}
public String format() {
return title + " - " + description;
}
@Override
public String toString() {
return "group("
+ (groupid == null ? "groupid: null" : groupid.toString())
+ ", "
+ (parent == null ? "parent: null" : parent.toString())
+ ", "
+ (meta == null ? "meta: null" : meta.toString())
+ ")";
}
public static Group EMPTY() {
return new Group();
}
public long getVersion() {
return meta.getVersion();
}
}

View File

@ -0,0 +1,50 @@
package mops.gruppen2.domain.model.group;
import lombok.ToString;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.IdMismatchException;
import java.time.LocalDateTime;
@Log4j2
@Value
@ToString
class GroupMeta {
long version;
String creator;
LocalDateTime creationDate;
GroupMeta setVersion(long version) throws IdMismatchException {
if (this.version >= version) {
throw new IdMismatchException("Die Gruppe ist bereits auf einem neueren Stand.");
}
if (this.version + 1 != version) {
throw new IdMismatchException("Es fehlen vorherige Events.");
}
return new GroupMeta(version, creator, creationDate);
}
GroupMeta setCreator(String userid) throws BadArgumentException {
if (creator != null) {
throw new BadArgumentException("Gruppe hat schon einen Ersteller.");
}
return new GroupMeta(version, userid, creationDate);
}
GroupMeta setCreationDate(LocalDateTime date) throws BadArgumentException {
if (creationDate != null) {
throw new BadArgumentException("Gruppe hat schon ein Erstellungsdatum.");
}
return new GroupMeta(version, creator, date);
}
static GroupMeta EMPTY() {
return new GroupMeta(0, null, null);
}
}

View File

@ -0,0 +1,40 @@
package mops.gruppen2.domain.model.group;
import lombok.Value;
//TODO: doooooodododo
@Value
class GroupOptions {
// Gruppe
boolean showClearname;
boolean hasBody;
boolean isLeavable;
boolean hasLink;
String customLogo;
String customBackground;
String customTitle;
// Integrations
boolean hasMaterialIntegration;
boolean hasTermineIntegration;
boolean hasPortfolioIntegration;
boolean hasForumsIntegration;
boolean hasModulesIntegration;
static GroupOptions DEFAULT() {
return new GroupOptions(true,
false,
true,
false,
null,
null,
null,
true,
true,
true,
true,
true);
}
}

View File

@ -0,0 +1,23 @@
package mops.gruppen2.domain.model.group;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Membership {
User user;
Role role;
// LocalDateTime age;
@Override
public String toString() {
return user.format() + ": " + role;
}
public Membership setRole(Role role) {
return new Membership(user, role);
}
}

View File

@ -0,0 +1,10 @@
package mops.gruppen2.domain.model.group;
public enum Role {
ADMIN,
REGULAR;
public Role toggle() {
return this == ADMIN ? REGULAR : ADMIN;
}
}

View File

@ -0,0 +1,7 @@
package mops.gruppen2.domain.model.group;
public enum Type {
PUBLIC,
PRIVATE,
LECTURE
}

View File

@ -0,0 +1,64 @@
package mops.gruppen2.domain.model.group;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
@Log4j2
@Value
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor
public class User {
@EqualsAndHashCode.Include
@Getter(AccessLevel.NONE)
@JsonProperty("id")
String userid;
@JsonProperty("givenname")
String givenname;
@JsonProperty("familyname")
String familyname;
@JsonProperty("email")
String email;
public User(KeycloakAuthenticationToken token) {
KeycloakPrincipal principal = (KeycloakPrincipal) token.getPrincipal();
userid = principal.getName();
givenname = principal.getKeycloakSecurityContext().getIdToken().getGivenName();
familyname = principal.getKeycloakSecurityContext().getIdToken().getFamilyName();
email = principal.getKeycloakSecurityContext().getIdToken().getEmail();
}
/**
* User identifizieren sich über die Id, mehr wird also manchmal nicht benötigt.
*
* @param userid Die User Id
*/
public User(String userid) {
this.userid = userid;
givenname = "";
familyname = "";
email = "";
}
public String getId() {
return userid;
}
public String format() {
return givenname + " " + familyname;
}
public boolean isMember(Group group) {
return group.getMembers().contains(this);
}
}

View File

@ -0,0 +1,8 @@
package mops.gruppen2.domain.model.group.wrapper;
import lombok.Value;
//TODO: do it
@Value
public class Body {
}

View File

@ -0,0 +1,27 @@
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Value
public class Description {
@NotNull
@Size(min = 4, max = 512)
@JsonProperty("value")
String value;
@Override
public String toString() {
return value;
}
@JsonIgnore
public static Description EMPTY() {
return new Description("EMPTY");
}
}

View File

@ -0,0 +1,27 @@
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Value
public class Limit {
@NotNull
@Min(1)
@Max(999_999)
@JsonProperty("value")
long value;
public static Limit DEFAULT() {
return new Limit(1);
}
@Override
public String toString() {
return String.valueOf(value);
}
}

View File

@ -0,0 +1,27 @@
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.NotNull;
import java.util.UUID;
@Value
public class Link {
@NotNull
@JsonProperty("value")
UUID value;
public Link(String value) {
this.value = UUID.fromString(value);
}
public static Link RANDOM() {
return new Link(UUID.randomUUID().toString());
}
public String getValue() {
return value.toString();
}
}

View File

@ -0,0 +1,36 @@
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.ToString;
import lombok.Value;
import mops.gruppen2.domain.service.helper.CommonHelper;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.beans.ConstructorProperties;
import java.util.UUID;
@Value
@ToString
public class Parent {
@NotNull
@JsonProperty("id")
UUID value;
@ConstructorProperties("id")
public Parent(@NotBlank @Size(min = 36, max = 36) String parentid) {
value = UUID.fromString(parentid);
}
public static Parent EMPTY() {
return new Parent("00000000-0000-0000-0000-000000000000");
}
@JsonIgnore
public boolean isEmpty() {
return CommonHelper.uuidIsEmpty(value);
}
}

View File

@ -0,0 +1,27 @@
package mops.gruppen2.domain.model.group.wrapper;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Value
public class Title {
@NotNull
@Size(min = 4, max = 128)
@JsonProperty("value")
String value;
@Override
public String toString() {
return value;
}
@JsonIgnore
public static Title EMPTY() {
return new Title("EMPTY");
}
}

View File

@ -0,0 +1,130 @@
package mops.gruppen2.domain.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.exception.BadPayloadException;
import mops.gruppen2.domain.service.helper.CommonHelper;
import mops.gruppen2.domain.service.helper.FileHelper;
import mops.gruppen2.persistance.EventRepository;
import mops.gruppen2.persistance.dto.EventDTO;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Log4j2
@RequiredArgsConstructor
@Service
public class EventStoreService {
private final EventRepository eventStore;
//########################################### SAVE ###########################################
/**
* Erzeugt ein DTO aus einem Event und speicher es.
*
* @param event Event, welches gespeichert wird
*/
public void saveEvent(Event event) {
eventStore.save(getDTOFromEvent(event));
}
public void saveAll(Event... events) {
for (Event event : events) {
eventStore.save(getDTOFromEvent(event));
}
}
//########################################### DTOs ###########################################
/**
* Erzeugt aus einem Event Objekt ein EventDTO Objekt.
*
* @param event Event, welches in DTO übersetzt wird
*
* @return EventDTO (Neues DTO)
*/
private static EventDTO getDTOFromEvent(Event event) {
try {
String payload = FileHelper.serializeEventJson(event);
return new EventDTO(null,
event.getGroupid().toString(),
event.getVersion(),
event.getExec(),
event.getTarget(),
Timestamp.valueOf(event.getDate()),
payload);
} catch (JsonProcessingException e) {
log.error("Event ({}) konnte nicht serialisiert werden!", event, e);
throw new BadPayloadException(EventStoreService.class.toString());
}
}
/**
* Erzeugt aus einer Liste von eventDTOs eine Liste von Events.
*
* @param eventDTOS Liste von DTOs
*
* @return Liste von Events
*/
private static List<Event> getEventsFromDTOs(List<EventDTO> eventDTOS) {
return eventDTOS.stream()
.map(EventStoreService::getEventFromDTO)
.collect(Collectors.toList());
}
private static Event getEventFromDTO(EventDTO dto) {
try {
return FileHelper.deserializeEventJson(dto.getEvent_payload());
} catch (JsonProcessingException e) {
log.error("Payload {} konnte nicht deserialisiert werden!", dto.getEvent_payload(), e);
throw new BadPayloadException(EventStoreService.class.toString());
}
}
// #################################### SIMPLE QUERIES #######################################
public List<Event> findAllEvents() {
return getEventsFromDTOs(eventStore.findAllEvents());
}
public List<Event> findGroupEvents(UUID groupId) {
return getEventsFromDTOs(eventStore.findGroupEvents(groupId.toString()));
}
public List<Event> findGroupEvents(List<UUID> ids) {
return ids.stream()
.map(id -> eventStore.findGroupEvents(id.toString()))
.map(EventStoreService::getEventsFromDTOs)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
public List<String> findGroupPayloads(UUID groupId) {
return eventStore.findGroupPayloads(groupId.toString());
}
public List<EventDTO> findGroupDTOs(UUID groupid) {
return eventStore.findGroupEvents(groupid.toString());
}
public List<UUID> findChangedGroups(long eventid) {
return CommonHelper.stringsToUUID(eventStore.findChangedGroupIds(eventid));
}
public long findMaxEventId() {
return eventStore.findMaxEventId();
}
}

View File

@ -0,0 +1,280 @@
package mops.gruppen2.domain.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.AddMemberEvent;
import mops.gruppen2.domain.event.CreateGroupEvent;
import mops.gruppen2.domain.event.DestroyGroupEvent;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.event.KickMemberEvent;
import mops.gruppen2.domain.event.SetDescriptionEvent;
import mops.gruppen2.domain.event.SetInviteLinkEvent;
import mops.gruppen2.domain.event.SetLimitEvent;
import mops.gruppen2.domain.event.SetParentEvent;
import mops.gruppen2.domain.event.SetTitleEvent;
import mops.gruppen2.domain.event.SetTypeEvent;
import mops.gruppen2.domain.event.UpdateRoleEvent;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Role;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Link;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import mops.gruppen2.domain.model.group.wrapper.Title;
import mops.gruppen2.domain.service.helper.ValidationHelper;
import mops.gruppen2.infrastructure.GroupCache;
import org.springframework.stereotype.Service;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Behandelt Aufgaben, welche sich auf eine Gruppe beziehen.
* Es werden übergebene Gruppen bearbeitet und dementsprechend Events erzeugt und gespeichert.
*/
@Log4j2
@RequiredArgsConstructor
@Service
public class GroupService {
private final GroupCache groupCache;
private final EventStoreService eventStoreService;
// ################################# GRUPPE ERSTELLEN ########################################
public Group createGroup(String exec) {
return createGroup(UUID.randomUUID(), exec, LocalDateTime.now());
}
public void initGroupMembers(Group group,
String exec,
String target,
User user,
Limit limit) {
addMember(group, exec, target, user);
updateRole(group, exec, target, Role.ADMIN);
setLimit(group, exec, limit);
}
public void initGroupMeta(Group group,
String exec,
Type type,
Parent parent) {
setType(group, exec, type);
setParent(group, exec, parent);
}
public void initGroupText(Group group,
String exec,
Title title,
Description description) {
setTitle(group, exec, title);
setDescription(group, exec, description);
}
// ################################### GRUPPEN ÄNDERN ########################################
/**
* Fügt eine Liste von Usern zu einer Gruppe hinzu.
* Duplikate werden übersprungen, die erzeugten Events werden gespeichert.
* Dabei wird das Teilnehmermaximum eventuell angehoben.
* Prüft, ob der User Admin ist.
*
* @param newUsers Userliste
* @param group Gruppe
* @param exec Ausführender User
*/
public void addUsersToGroup(Group group, String exec, List<User> newUsers) {
List<User> users = newUsers.stream().distinct().collect(Collectors.toUnmodifiableList());
setLimit(group, exec, getAdjustedUserLimit(users, group));
users.forEach(newUser -> addUserSilent(group, exec, newUser.getId(), newUser));
}
/**
* Ermittelt ein passendes Teilnehmermaximum.
* Reicht das alte Maximum, wird dieses zurückgegeben.
* Ansonsten wird ein erhöhtes Maximum zurückgegeben.
*
* @param newUsers Neue Teilnehmer
* @param group Bestehende Gruppe, welche verändert wird
*
* @return Das neue Teilnehmermaximum
*/
private static Limit getAdjustedUserLimit(List<User> newUsers, Group group) {
return new Limit(Math.max((long) group.size() + newUsers.size(), group.getLimit()));
}
/**
* Wechselt die Rolle eines Teilnehmers von Admin zu Member oder andersherum.
* Überprüft, ob der User Mitglied ist und ob er der letzte Admin ist.
*
* @param target Teilnehmer, welcher geändert wird
* @param group Gruppe, in welcher sih der Teilnehmer befindet
*
* @throws EventException Falls der User nicht gefunden wird
*/
public void toggleMemberRole(Group group, String exec, String target) {
ValidationHelper.throwIfNoMember(group, target);
updateRole(group, exec, target, group.getRole(target).toggle());
}
// ################################# SINGLE EVENTS ###########################################
// Spezifische Events werden erzeugt, validiert, auf die Gruppe angewandt und gespeichert
/**
* Erzeugt eine Gruppe, speichert diese und gibt diese zurück.
*/
private Group createGroup(UUID groupid, String exec, LocalDateTime date) {
Event event = new CreateGroupEvent(groupid,
exec,
date);
Group group = Group.EMPTY();
applyAndSave(group, event);
return group;
}
/**
* Dasselbe wie addUser(), aber exceptions werden abgefangen und nicht geworfen.
*/
private void addUserSilent(Group group, String exec, String target, User user) {
try {
addMember(group, exec, target, user);
} catch (Exception e) {
log.debug("Doppelter User {} wurde nicht zu Gruppe {} hinzugefügt!", user, group);
}
}
/**
* Erzeugt, speichert ein AddUserEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer schon Mitglied ist und ob Gruppe voll ist.
*/
public void addMember(Group group, String exec, String target, User user) {
applyAndSave(group, new AddMemberEvent(group.getId(), exec, target, user));
}
/**
* Erzeugt, speichert ein DeleteUserEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Mitglied ist und ob er der letzte Admin ist.
*/
public void kickMember(Group group, String exec, String target) {
applyAndSave(group, new KickMemberEvent(group.getId(), exec, target));
if (group.isEmpty()) {
deleteGroup(group, exec);
}
}
/**
* Erzeugt, speichert ein DeleteGroupEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Admin ist.
*/
public void deleteGroup(Group group, String exec) {
if (!group.exists()) {
return;
}
applyAndSave(group, new DestroyGroupEvent(group.getId(), exec));
}
/**
* Erzeugt, speichert ein UpdateTitleEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Admin ist und ob der Titel valide ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
public void setTitle(Group group, String exec, @Valid Title title) {
if (group.getTitle().equals(title.getValue())) {
return;
}
applyAndSave(group, new SetTitleEvent(group.getId(), exec, title));
}
/**
* Erzeugt, speichert ein UpdateDescriptiopnEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Admin ist und ob die Beschreibung valide ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
public void setDescription(Group group, String exec, @Valid Description description) {
if (group.getDescription().equals(description.getValue())) {
return;
}
applyAndSave(group, new SetDescriptionEvent(group.getId(), exec, description));
}
/**
* Erzeugt, speichert ein UpdateRoleEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Mitglied ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
private void updateRole(Group group, String exec, String target, Role role) {
if (group.memberHasRole(target, role)) {
return;
}
applyAndSave(group, new UpdateRoleEvent(group.getId(), exec, target, role));
}
/**
* Erzeugt, speichert ein UpdateUserLimitEvent und wendet es auf eine Gruppe an.
* Prüft, ob der Nutzer Admin ist und ob das Limit valide ist.
* Bei keiner Änderung wird nichts erzeugt.
*/
public void setLimit(Group group, String exec, @Valid Limit userLimit) {
if (userLimit.getValue() == group.getLimit()) {
return;
}
applyAndSave(group, new SetLimitEvent(group.getId(), exec, userLimit));
}
public void setParent(Group group, String exec, Parent parent) {
if (parent.getValue() == group.getParent()) {
return;
}
applyAndSave(group, new SetParentEvent(group.getId(), exec, parent));
}
//TODO: UI Link regenerieren button
public void setLink(Group group, String exec, @Valid Link link) {
if (group.getLink().equals(link.getValue())) {
return;
}
applyAndSave(group, new SetInviteLinkEvent(group.getId(), exec, link));
}
private void setType(Group group, String exec, Type type) {
if (group.getType() == type) {
return;
}
applyAndSave(group, new SetTypeEvent(group.getId(), exec, type));
}
private void applyAndSave(Group group, Event event) throws EventException {
event.init(group.version() + 1);
event.apply(group, groupCache);
eventStoreService.saveEvent(event);
}
}

View File

@ -0,0 +1,68 @@
package mops.gruppen2.domain.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.infrastructure.GroupCache;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Log4j2
public class SearchService {
private final GroupCache groupCache;
/**
* Filtert alle öffentliche Gruppen nach dem Suchbegriff und gibt diese als sortierte Liste zurück.
* Groß- und Kleinschreibung wird nicht beachtet.
* Der Suchbegriff wird im Gruppentitel und in der Beschreibung gesucht.
*
* @param search Der Suchstring
*
* @return Liste von projizierten Gruppen
*
* @throws EventException Projektionsfehler
*/
public List<Group> searchString(String search, String principal) {
List<Group> groups = new ArrayList<>();
groups.addAll(groupCache.publics());
groups.addAll(groupCache.lectures());
groups = removeUserGroups(groups, principal);
if (search.isEmpty()) {
return groups;
}
log.debug("Es wurde gesucht nach: {}", search);
return groups.stream()
.filter(group -> group.format().toLowerCase().contains(search.toLowerCase()))
.collect(Collectors.toList());
}
public List<Group> searchType(Type type, String principal) {
log.debug("Es wurde gesucht nach: {}", type);
if (type == Type.LECTURE) {
return removeUserGroups(groupCache.lectures(), principal);
}
if (type == Type.PUBLIC) {
return removeUserGroups(groupCache.publics(), principal);
}
return Collections.emptyList();
}
private static List<Group> removeUserGroups(List<Group> groups, String principal) {
return groups.stream()
.filter(group -> !group.isMember(principal))
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,26 @@
package mops.gruppen2.domain.service.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.api.GroupRequestWrapper;
import mops.gruppen2.infrastructure.api.GroupWrapper;
import java.util.List;
import java.util.stream.Collectors;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class APIHelper {
public static GroupRequestWrapper wrap(long status, List<Group> groupList) {
return new GroupRequestWrapper(status, wrap(groupList));
}
public static List<GroupWrapper> wrap(List<Group> groups) {
return groups.stream()
.map(GroupWrapper::new)
.collect(Collectors.toUnmodifiableList());
}
}

View File

@ -0,0 +1,24 @@
package mops.gruppen2.domain.service.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class CommonHelper {
public static boolean uuidIsEmpty(UUID uuid) {
return "00000000-0000-0000-0000-000000000000".equals(uuid.toString());
}
public static List<UUID> stringsToUUID(List<String> changedGroupIds) {
return changedGroupIds.stream()
.map(UUID::fromString)
.collect(Collectors.toUnmodifiableList());
}
}

View File

@ -0,0 +1,148 @@
package mops.gruppen2.domain.service.helper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.exception.EventException;
import mops.gruppen2.domain.exception.GroupNotFoundException;
import mops.gruppen2.domain.exception.WrongFileException;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.persistance.dto.EventDTO;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class FileHelper {
// ######################################## CSV #############################################
public static List<User> readCsvFile(MultipartFile file) throws EventException {
if (file == null || file.isEmpty()) {
return Collections.emptyList();
}
try {
List<User> userList = readCsv(file.getInputStream());
return userList.stream()
.distinct()
.collect(Collectors.toList()); //filter duplicates from list
} catch (IOException e) {
log.error("File konnte nicht gelesen werden!", e);
throw new WrongFileException(file.getOriginalFilename());
}
}
private static List<User> readCsv(InputStream stream) throws IOException {
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(User.class).withHeader().withColumnReordering(true);
ObjectReader reader = mapper.readerFor(User.class).with(schema);
return reader.<User>readValues(stream).readAll();
}
public static String writeCsvUserList(List<User> members) {
StringBuilder builder = new StringBuilder();
builder.append("id,givenname,familyname,email\n");
members.forEach(user -> builder.append(user.getId())
.append(",")
.append(user.getGivenname())
.append(",")
.append(user.getFamilyname())
.append(",")
.append(user.getEmail())
.append("\n"));
return builder.toString();
}
// ########################################## JSON ###########################################
/**
* Übersetzt eine Java-Event-Repräsentation zu einem JSON-Event-Payload.
*
* @param event Java-Event-Repräsentation
*
* @return JSON-Event-Payload als String
*
* @throws JsonProcessingException Bei JSON Fehler
*/
public static String serializeEventJson(Event event) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
String payload = mapper.writeValueAsString(event);
log.trace(payload);
return payload;
}
/**
* Übersetzt eine JSON-Event-Payload zu einer Java-Event-Repräsentation.
*
* @param json JSON-Event-Payload als String
*
* @return Java-Event-Repräsentation
*
* @throws JsonProcessingException Bei JSON Fehler
*/
public static Event deserializeEventJson(String json) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
Event event = mapper.readValue(json, Event.class);
log.trace(event);
return event;
}
// ############################################### TXT #######################################
public static String payloadsToPlain(List<String> payloads) {
return payloads.stream()
.map(payload -> payload + "\n")
.reduce((String payloadA, String payloadB) -> payloadA + payloadB)
.orElseThrow(() -> new GroupNotFoundException("Keine Payloads gefunden."));
}
public static String eventDTOsToSql(List<EventDTO> dtos) {
StringBuilder builder = new StringBuilder();
builder.append("INSERT INTO event(group_id, group_version, exec_id, target_id, event_date, event_payload)\nVALUES\n");
dtos.forEach(dto -> builder.append("('")
.append(dto.getGroup_id())
.append("','")
.append(dto.getGroup_version())
.append("','")
.append(dto.getExec_id())
.append("','")
.append(dto.getTarget_id())
.append("','")
.append(dto.getEvent_date())
.append("','")
.append(dto.getEvent_payload())
.append("'),\n"));
builder.replace(builder.length() - 2, builder.length(), ";");
return builder.toString();
}
// ############################################### SQL #######################################
}

View File

@ -0,0 +1,67 @@
package mops.gruppen2.domain.service.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.event.Event;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.infrastructure.GroupCache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Liefert verschiedene Projektionen auf Gruppen.
* Benötigt ausschließlich den EventStoreService.
*/
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ProjectionHelper {
public static List<Group> project(List<Event> events) {
Map<UUID, Group> groups = new HashMap<>();
if (events.isEmpty()) {
return Collections.emptyList();
}
log.trace(groups);
log.trace(events);
events.forEach(event -> event.apply(getOrCreateGroup(groups, event.getGroupid())));
return new ArrayList<>(groups.values());
}
public static void project(Map<UUID, Group> groups, List<Event> events, GroupCache cache) {
if (events.isEmpty()) {
return;
}
log.trace(groups);
log.trace(events);
events.forEach(event -> event.apply(getOrCreateGroup(groups, event.getGroupid()), cache));
}
/**
* Gibt die Gruppe mit der richtigen Id aus der übergebenen Map wieder, existiert diese nicht
* wird die Gruppe erstellt und der Map hizugefügt.
*
* @param groups Map aus GruppenIds und Gruppen
* @param groupId Die Id der Gruppe, die zurückgegeben werden soll
*
* @return Die gesuchte Gruppe
*/
private static Group getOrCreateGroup(Map<UUID, Group> groups, UUID groupId) {
if (!groups.containsKey(groupId)) {
groups.put(groupId, Group.EMPTY());
}
return groups.get(groupId);
}
}

View File

@ -0,0 +1,27 @@
package mops.gruppen2.domain.service.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import mops.gruppen2.domain.model.group.Membership;
import mops.gruppen2.domain.model.group.Role;
import java.util.List;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SortHelper {
public static List<Membership> sortByMemberRole(List<Membership> memberships) {
memberships.sort((Membership m1, Membership m2) -> {
if (m1.getRole() == Role.ADMIN) {
return -1;
}
if (m2.getRole() == Role.ADMIN) {
return 1;
}
return 0;
});
return memberships;
}
}

View File

@ -0,0 +1,84 @@
package mops.gruppen2.domain.service.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.BadArgumentException;
import mops.gruppen2.domain.exception.GroupFullException;
import mops.gruppen2.domain.exception.LastAdminException;
import mops.gruppen2.domain.exception.NoAccessException;
import mops.gruppen2.domain.exception.UserExistsException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
@Log4j2
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ValidationHelper {
public static boolean checkIfLastMember(Group group, String userid) {
return group.isMember(userid) && group.size() == 1;
}
/**
* Überprüft, ob ein User in einer Gruppe Admin ist.
*/
public static boolean checkIfAdmin(Group group, String userid) {
if (group.isMember(userid)) {
return group.isAdmin(userid);
}
return false;
}
public static boolean checkIfLastAdmin(Group group, String userid) {
return checkIfAdmin(group, userid) && group.getAdmins().size() == 1;
}
// ######################################## THROW ############################################
public static void throwIfMember(Group group, String userid) throws UserExistsException {
if (group.isMember(userid)) {
log.error("Benutzer {} ist schon in Gruppe {}", userid, group);
throw new UserExistsException(userid);
}
}
public static void throwIfNoMember(Group group, String userid) throws UserNotFoundException {
if (!group.isMember(userid)) {
log.error("Benutzer {} ist nicht in Gruppe {}!", userid, group);
throw new UserNotFoundException(userid);
}
}
public static void throwIfNoAdmin(Group group, String userid) throws NoAccessException {
if (!checkIfAdmin(group, userid)) {
log.error("User {} ist kein Admin in Gruppe {}!", userid, group);
throw new NoAccessException(group.getId().toString());
}
}
/**
* Schmeißt keine Exception, wenn der User der letzte User ist.
*/
public static void throwIfLastAdmin(Group group, String userid) throws LastAdminException {
if (!checkIfLastMember(group, userid) && checkIfLastAdmin(group, userid)) {
throw new LastAdminException("Du bist letzter Admin!");
}
}
public static void throwIfGroupFull(Group group) throws GroupFullException {
if (group.isFull()) {
log.error("Die Gruppe {} ist voll!", group);
throw new GroupFullException(group.getId().toString());
}
}
public static void validateCreateForm(KeycloakAuthenticationToken token, Type type) {
if (!token.getAccount().getRoles().contains("orga") && type == Type.LECTURE) {
throw new BadArgumentException("Nur Orga kann Veranstaltungen erstellen.");
}
}
}

View File

@ -0,0 +1,18 @@
package mops.gruppen2.infrastructure;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Component
public class ApplicationInit {
private final GroupCache groupCache;
@EventListener(ApplicationReadyEvent.class)
public void init() {
groupCache.init();
}
}

View File

@ -0,0 +1,208 @@
package mops.gruppen2.infrastructure;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.domain.exception.GroupNotFoundException;
import mops.gruppen2.domain.exception.IdMismatchException;
import mops.gruppen2.domain.exception.UserNotFoundException;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.service.EventStoreService;
import mops.gruppen2.domain.service.helper.ProjectionHelper;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Cached alle existierenden Gruppen und einige Beziehungen.
* Gruppen können nach Typ angefragt werden, nach ID, nach Link oder nach User.
* Der Cache wird von den Events aktualisiert.
* Beim Aufruf der init() Methode werden alle bisherigen Events projiziert und die Gruppen gespeichert.
* Die Komplette Anwendung verwendet eine Instanz des Caches.
*/
@Log4j2
@RequiredArgsConstructor
@Component
@Scope("singleton")
public class GroupCache {
private final EventStoreService eventStoreService;
private final Map<UUID, Group> groups = new HashMap<>();
private final Map<String, Group> links = new HashMap<>();
private final Map<String, List<Group>> users = new HashMap<>(); // Wird vielleicht zu groß?
private final Map<Type, List<Group>> types = new EnumMap<>(Type.class);
// ######################################## CACHE ###########################################
void init() {
ProjectionHelper.project(groups, eventStoreService.findAllEvents(), this);
}
// ########################################### GETTERS #######################################
public Group group(UUID groupid) {
if (!groups.containsKey(groupid)) {
throw new GroupNotFoundException("Gruppe ist nicht im Cache.");
}
return groups.get(groupid);
}
public Group group(String link) {
if (!links.containsKey(link)) {
throw new GroupNotFoundException("Link ist nicht im Cache.");
}
return links.get(link);
}
public List<Group> groups() {
if (groups.isEmpty()) {
return Collections.emptyList();
}
return List.copyOf(groups.values());
}
public List<Group> userGroups(String userid) {
if (!users.containsKey(userid)) {
return Collections.emptyList();
}
return Collections.unmodifiableList(users.get(userid));
}
public List<Group> userLectures(String userid) {
return userGroups(userid).stream()
.filter(Group::isLecture)
.collect(Collectors.toUnmodifiableList());
}
public List<Group> userPublics(String userid) {
return userGroups(userid).stream()
.filter(Group::isPublic)
.collect(Collectors.toUnmodifiableList());
}
public List<Group> userPrivates(String userid) {
return userGroups(userid).stream()
.filter(Group::isPrivate)
.collect(Collectors.toUnmodifiableList());
}
public List<Group> publics() {
if (!types.containsKey(Type.PUBLIC)) {
return Collections.emptyList();
}
return Collections.unmodifiableList(types.get(Type.PUBLIC));
}
public List<Group> privates() {
if (!types.containsKey(Type.PRIVATE)) {
return Collections.emptyList();
}
return Collections.unmodifiableList(types.get(Type.PRIVATE));
}
public List<Group> lectures() {
if (!types.containsKey(Type.LECTURE)) {
return Collections.emptyList();
}
return Collections.unmodifiableList(types.get(Type.LECTURE));
}
// ######################################## SETTERS ##########################################
public void usersPut(String userid, Group group) {
if (!group.isMember(userid)) {
throw new UserNotFoundException("User ist kein Mitglied, Gruppe nicht gecached.");
}
if (!users.containsKey(userid)) {
users.put(userid, new ArrayList<>());
log.debug("Ein User wurde dem Cache hinzugefügt.");
}
users.get(userid).add(group);
}
public void usersRemove(String target, Group group) {
if (!users.containsKey(target)) {
return;
}
users.get(target).remove(group);
}
public void groupsPut(UUID groupid, Group group) {
if (group.getId() != groupid) {
throw new IdMismatchException("ID passt nicht zu Gruppe, Gruppe nicht gecached.");
}
groups.put(groupid, group);
}
public void groupsRemove(UUID groupid, Group group) {
if (!groups.containsKey(groupid)) {
return;
}
groups.remove(groupid);
links.remove(group.getLink());
group.getMembers().forEach(user -> users.get(user.getId()).removeIf(usergroup -> !usergroup.exists()));
types.get(group.getType()).removeIf(typegroup -> !typegroup.exists());
}
public void linksPut(String link, Group group) {
if (!link.equals(group.getLink())) {
throw new IdMismatchException("Link passt nicht zu Gruppe, Gruppe nicht gecached.");
}
links.put(link, group);
}
public void linksRemove(String link) {
if (!links.containsKey(link)) {
return;
}
links.remove(link);
}
public void typesPut(Type type, Group group) {
if (!types.containsKey(type)) {
types.put(type, new ArrayList<>());
log.debug("Ein Typ wurde dem Cache hinzugefügt.");
}
if (group.getType() != type) {
throw new IdMismatchException("Typ passt nicht zu Gruppe, Gruppe nicht gecached.");
}
types.get(type).add(group);
}
public void typesRemove(Type type, Group group) {
if (!types.containsKey(type)) {
return;
}
types.get(type).remove(group);
}
}

View File

@ -0,0 +1,36 @@
package mops.gruppen2.infrastructure;
import lombok.RequiredArgsConstructor;
import mops.gruppen2.domain.Account;
import mops.gruppen2.domain.model.group.Role;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
@RequiredArgsConstructor
@ControllerAdvice
public class ModelAttributeControllerAdvice {
// Add modelAttributes before each @RequestMapping
@ModelAttribute
public void modelAttributes(KeycloakAuthenticationToken token,
Model model) {
// Prevent NullPointerException if not logged in
if (token != null) {
model.addAttribute("account", new Account(token));
model.addAttribute("principal", new User(token));
}
// Add enums
model.addAttribute("REGULAR", Role.REGULAR);
model.addAttribute("ADMIN", Role.ADMIN);
model.addAttribute("PUBLIC", Type.PUBLIC);
model.addAttribute("PRIVATE", Type.PRIVATE);
model.addAttribute("LECTURE", Type.LECTURE);
}
}

View File

@ -1,18 +1,17 @@
package mops.gruppen2.domain.api; package mops.gruppen2.infrastructure.api;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import mops.gruppen2.domain.Group;
import java.util.List; import java.util.List;
/** /**
* Kombiniert den Status und die Gruppenliste zur ausgabe über die API. * Kombiniert den Status und die Gruppenliste zur ausgabe über die API.
*/ */
@AllArgsConstructor
@Getter @Getter
@AllArgsConstructor
public class GroupRequestWrapper { public class GroupRequestWrapper {
private final Long status; private final long version;
private final List<Group> groupList; private final List<GroupWrapper> groups;
} }

View File

@ -0,0 +1,31 @@
package mops.gruppen2.infrastructure.api;
import lombok.Value;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import java.util.List;
import java.util.UUID;
@Value
public class GroupWrapper {
UUID groupid;
Type type;
UUID parent;
String title;
String description;
List<User> admins;
List<User> regulars;
public GroupWrapper(Group group) {
groupid = group.getId();
type = group.getType();
parent = group.getParent();
title = group.getTitle();
description = group.getDescription();
admins = group.getAdmins();
regulars = group.getRegulars();
}
}

View File

@ -0,0 +1,83 @@
package mops.gruppen2.infrastructure.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.service.EventStoreService;
import mops.gruppen2.domain.service.helper.APIHelper;
import mops.gruppen2.domain.service.helper.ProjectionHelper;
import mops.gruppen2.infrastructure.GroupCache;
import mops.gruppen2.infrastructure.api.GroupRequestWrapper;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Api zum Datenabgleich.
*/
@Log4j2
@TraceMethodCalls
@RequiredArgsConstructor
@RestController
@RequestMapping("/gruppen2/api")
public class APIController {
private final GroupCache cache;
private final EventStoreService eventStoreService;
/**
* Erzeugt eine Liste aus Gruppen, welche sich seit einer übergebenen Event-Id geändert haben.
* Die Gruppen werden vollständig projiziert, enthalten also alle Informationen zum entsprechenden Zeitpunkt.
*
* @param eventId Die Event-ID, welche der Anfragesteller beim letzten Aufruf erhalten hat
*/
//TODO: sollte den cache benutzen, am besten wäre eine groupversion, welche der eventid
//TODO: entspricht, dann kann man leicht alle geänderten gruppen finden
@GetMapping("/update/{id}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt veränderte Gruppen zurück")
public GroupRequestWrapper getApiUpdate(@ApiParam("Letzte gespeicherte EventId des Anfragestellers")
@PathVariable("id") long eventId) {
return APIHelper.wrap(eventStoreService.findMaxEventId(),
ProjectionHelper.project(eventStoreService.findGroupEvents(eventStoreService.findChangedGroups(eventId))));
}
/**
* Gibt die Gruppen-IDs von Gruppen, in welchen der übergebene Nutzer teilnimmt, zurück.
*/
@GetMapping("/usergroups/{id}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt Gruppen zurück, in welchen ein Nutzer teilnimmt")
public List<String> getApiUserGroups(@ApiParam("Nutzer-Id")
@PathVariable("id") String userId) {
return cache.userGroups(userId).stream()
.map(Group::getId)
.map(UUID::toString)
.collect(Collectors.toUnmodifiableList());
}
/**
* Konstruiert eine einzelne, vollständige Gruppe.
*/
@GetMapping("/group/{id}")
@Secured("ROLE_api_user")
@ApiOperation("Gibt die Gruppe mit der als Parameter mitgegebenden groupId zurück")
public Group getApiGroup(@ApiParam("Gruppen-Id der gefordeten Gruppe")
@PathVariable("id") String groupId) {
return cache.group(UUID.fromString(groupId));
}
}

View File

@ -0,0 +1,75 @@
package mops.gruppen2.infrastructure.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.Type;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Parent;
import mops.gruppen2.domain.model.group.wrapper.Title;
import mops.gruppen2.domain.service.GroupService;
import mops.gruppen2.domain.service.helper.FileHelper;
import mops.gruppen2.domain.service.helper.ValidationHelper;
import mops.gruppen2.infrastructure.GroupCache;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.security.RolesAllowed;
import javax.validation.Valid;
@SuppressWarnings("SameReturnValue")
@Log4j2
@TraceMethodCalls
@RequiredArgsConstructor
@Controller
@RequestMapping("/gruppen2")
public class GroupCreationController {
private final GroupCache groupCache;
private final GroupService groupService;
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping("/create")
public String getCreate(Model model) {
model.addAttribute("lectures", groupCache.lectures());
return "create";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/create")
public String postCreateOrga(KeycloakAuthenticationToken token,
@RequestParam("type") Type type,
@RequestParam("parent") @Valid Parent parent,
@RequestParam("title") @Valid Title title,
@RequestParam("description") @Valid Description description,
@RequestParam("limit") @Valid Limit limit,
@RequestParam(value = "file", required = false) MultipartFile file) {
// Zusätzlicher check: studentin kann keine lecture erstellen
ValidationHelper.validateCreateForm(token, type);
String principal = token.getName();
Group group = groupService.createGroup(principal);
groupService.initGroupMembers(group, principal, principal, new User(token), limit);
groupService.initGroupMeta(group, principal, type, parent);
groupService.initGroupText(group, principal, title, description);
// ROLE_studentin kann kein CSV importieren
if (token.getAccount().getRoles().contains("orga")) {
groupService.addUsersToGroup(group, principal, FileHelper.readCsvFile(file));
}
return "redirect:/gruppen2/details/" + group.getId();
}
}

View File

@ -0,0 +1,296 @@
package mops.gruppen2.infrastructure.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCalls;
import mops.gruppen2.domain.model.group.Group;
import mops.gruppen2.domain.model.group.User;
import mops.gruppen2.domain.model.group.wrapper.Description;
import mops.gruppen2.domain.model.group.wrapper.Limit;
import mops.gruppen2.domain.model.group.wrapper.Title;
import mops.gruppen2.domain.service.EventStoreService;
import mops.gruppen2.domain.service.GroupService;
import mops.gruppen2.domain.service.helper.FileHelper;
import mops.gruppen2.domain.service.helper.ValidationHelper;
import mops.gruppen2.infrastructure.GroupCache;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.UUID;
@SuppressWarnings("SameReturnValue")
@Log4j2
@TraceMethodCalls
@RequiredArgsConstructor
@Controller
@RequestMapping("/gruppen2")
public class GroupDetailsController {
private final GroupCache groupCache;
private final GroupService groupService;
private final EventStoreService eventStoreService;
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping("/details/{id}")
public String getDetailsPage(KeycloakAuthenticationToken token,
Model model,
@PathVariable("id") String groupId) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
// Parent Badge
Group parent = Group.EMPTY();
if (group.hasParent()) {
parent = groupCache.group(group.getParent());
}
model.addAttribute("group", group);
model.addAttribute("parent", parent);
// Detailseite für nicht-Mitglieder
if (!group.isMember(principal)) {
return "preview";
}
return "details";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/details/{id}/join")
public String postDetailsJoin(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
if (group.isMember(principal)) {
return "redirect:/gruppen2/details/" + groupId;
}
groupService.addMember(group, principal, principal, new User(token));
return "redirect:/gruppen2/details/" + groupId;
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/details/{id}/leave")
public String postDetailsLeave(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
groupService.kickMember(group, principal, principal);
return "redirect:/gruppen2";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping("details/{id}/history")
public String getDetailsHistory(KeycloakAuthenticationToken token,
Model model,
@PathVariable("id") String groupId) {
model.addAttribute("events",
eventStoreService.findGroupEvents(UUID.fromString(groupId)));
return "log";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping(value = "details/{id}/export/history/plain", produces = "text/plain;charset=UTF-8")
public void getDetailsExportHistoryPlain(HttpServletResponse response,
@PathVariable("id") String groupId) {
String filename = "eventlog-" + groupId + ".txt";
response.setContentType("text/txt;charset=UTF-8");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + filename + "\"");
try {
response.getWriter()
.write(FileHelper.payloadsToPlain(
eventStoreService.findGroupPayloads(UUID.fromString(groupId))));
} catch (IOException e) {
log.error("Payloads konnten nicht geschrieben werden.", e);
}
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping(value = "details/{id}/export/history/sql", produces = "application/sql;charset=UTF-8")
public void getDetailsExportHistorySql(HttpServletResponse response,
@PathVariable("id") String groupId) {
String filename = "data.sql";
response.setContentType("application/sql;charset=UTF-8");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + filename + "\"");
try {
response.getWriter()
.write(FileHelper.eventDTOsToSql(
eventStoreService.findGroupDTOs(UUID.fromString(groupId))));
} catch (IOException e) {
log.error("Payloads konnten nicht geschrieben werden.", e);
}
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping(value = "details/{id}/export/members", produces = "text/csv;charset=UTF-8")
public void getDetailsExportMembers(HttpServletResponse response,
@PathVariable("id") String groupId) {
String filename = "teilnehmer-" + groupId + ".csv";
response.setContentType("text/csv;charset=UTF-8");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + filename + "\"");
try {
response.getWriter()
.print(FileHelper.writeCsvUserList(groupCache.group(UUID.fromString(groupId)).getMembers()));
} catch (IOException e) {
log.error("Teilnehmerliste konnte nicht geschrieben werden.", e);
}
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping("/details/{id}/edit")
public String getDetailsEdit(KeycloakAuthenticationToken token,
Model model,
HttpServletRequest request,
@PathVariable("id") String groupId) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
// Invite Link
String actualURL = request.getRequestURL().toString();
String serverURL = actualURL.substring(0, actualURL.indexOf("gruppen2/"));
String link = serverURL + "gruppen2/join/" + group.getLink();
ValidationHelper.throwIfNoAdmin(group, principal);
model.addAttribute("group", group);
model.addAttribute("link", link);
return "edit";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/details/{id}/edit/meta")
public String postDetailsEditMeta(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@Valid Title title,
@Valid Description description) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
groupService.setTitle(group, principal, title);
groupService.setDescription(group, principal, description);
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/details/{id}/edit/userlimit")
public String postDetailsEditUserLimit(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@Valid Limit limit) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
groupService.setLimit(group, principal, limit);
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@RolesAllowed("ROLE_orga")
@PostMapping("/details/{id}/edit/csv")
public String postDetailsEditCsv(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@RequestParam(value = "file", required = false) MultipartFile file) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
groupService.addUsersToGroup(group, principal, FileHelper.readCsvFile(file));
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/details/{id}/edit/role/{userid}")
public String postDetailsEditRole(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@PathVariable("userid") String target) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
ValidationHelper.throwIfNoAdmin(group, principal);
if (target.equals(principal)) {
ValidationHelper.throwIfLastAdmin(group, principal);
}
groupService.toggleMemberRole(group, principal, target);
// Falls sich der User selbst die Rechte genommen hat
if (!ValidationHelper.checkIfAdmin(group, principal)) {
return "redirect:/gruppen2/details/" + groupId;
}
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/details/{id}/edit/delete/{userid}")
public String postDetailsEditDelete(KeycloakAuthenticationToken token,
@PathVariable("id") String groupId,
@PathVariable("userid") String target) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupId));
ValidationHelper.throwIfNoAdmin(group, principal);
// Der eingeloggte User kann sich nicht selbst entfernen (er kann aber verlassen)
if (!principal.equals(target)) {
groupService.kickMember(group, principal, target);
}
return "redirect:/gruppen2/details/" + groupId + "/edit";
}
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@PostMapping("/details/{id}/edit/destroy")
public String postDetailsEditDestroy(KeycloakAuthenticationToken token,
@PathVariable("id") String groupid) {
String principal = token.getName();
Group group = groupCache.group(UUID.fromString(groupid));
groupService.deleteGroup(group, principal);
return "redirect:/gruppen2";
}
//TODO: Method + view for /details/{id}/member/{id}
}

View File

@ -1,10 +1,10 @@
package mops.gruppen2.controller; package mops.gruppen2.infrastructure.controller;
import mops.gruppen2.domain.Account; import lombok.RequiredArgsConstructor;
import mops.gruppen2.domain.User; import lombok.extern.log4j.Log4j2;
import mops.gruppen2.aspect.annotation.TraceMethodCall;
import mops.gruppen2.domain.exception.PageNotFoundException; import mops.gruppen2.domain.exception.PageNotFoundException;
import mops.gruppen2.service.KeyCloakService; import mops.gruppen2.infrastructure.GroupCache;
import mops.gruppen2.service.UserService;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@ -14,31 +14,29 @@ import javax.annotation.security.RolesAllowed;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@SuppressWarnings("SameReturnValue")
@Log4j2
@RequiredArgsConstructor
@Controller @Controller
public class GruppenfindungController { public class GruppenfindungController {
private final UserService userService; private final GroupCache groupCache;
public GruppenfindungController(UserService userService) {
this.userService = userService;
}
// For convenience
@GetMapping("") @GetMapping("")
public String redirect() { public String redirect() {
return "redirect:/gruppen2"; return "redirect:/gruppen2";
} }
@RolesAllowed({"ROLE_orga", "ROLE_studentin", "ROLE_actuator"}) @TraceMethodCall
@RolesAllowed({"ROLE_orga", "ROLE_studentin"})
@GetMapping("/gruppen2") @GetMapping("/gruppen2")
public String index(KeycloakAuthenticationToken token, public String getIndexPage(KeycloakAuthenticationToken token,
Model model) { Model model) {
Account account = KeyCloakService.createAccountFromPrincipal(token); model.addAttribute("lectures", groupCache.userLectures(token.getName()));
User user = new User(account); model.addAttribute("publics", groupCache.userPublics(token.getName()));
model.addAttribute("privates", groupCache.userPrivates(token.getName()));
model.addAttribute("account", account);
model.addAttribute("gruppen", userService.getUserGroups(user));
model.addAttribute("user", user);
return "index"; return "index";
} }

Some files were not shown because too many files have changed in this diff Show More