Skip to content
Permalink
Browse files

Merge remote-tracking branch 'origin/master' into feature/user-detail…

…s-db
  • Loading branch information
erikhofer committed Nov 26, 2019
2 parents 98dd2c0 + f1d9ff9 commit e9d5fd09c196044a8708944d5a750e553d4eda2c
Showing with 450 additions and 2 deletions.
  1. +9 −1 build.gradle
  2. +18 −0 src/main/kotlin/de/code_freak/codefreak/config/GraphQLConfiguration.kt
  3. +8 −0 src/main/kotlin/de/code_freak/codefreak/config/SecurityConfiguration.kt
  4. +44 −0 src/main/kotlin/de/code_freak/codefreak/graphql/DateTimeConverter.kt
  5. +34 −0 src/main/kotlin/de/code_freak/codefreak/graphql/ErrorHandler.kt
  6. +22 −0 src/main/kotlin/de/code_freak/codefreak/graphql/ScalarTypes.kt
  7. +13 −0 src/main/kotlin/de/code_freak/codefreak/graphql/SchemaGeneratorHooksImpl.kt
  8. +41 −0 src/main/kotlin/de/code_freak/codefreak/graphql/SchemaPrinter.kt
  9. +16 −0 src/main/kotlin/de/code_freak/codefreak/graphql/ServiceAccess.kt
  10. +40 −0 src/main/kotlin/de/code_freak/codefreak/graphql/UuidConverter.kt
  11. +25 −0 src/main/kotlin/de/code_freak/codefreak/graphql/api/AnswerApi.kt
  12. +56 −0 src/main/kotlin/de/code_freak/codefreak/graphql/api/AssignmentApi.kt
  13. +31 −0 src/main/kotlin/de/code_freak/codefreak/graphql/api/AuthApi.kt
  14. +25 −0 src/main/kotlin/de/code_freak/codefreak/graphql/api/EvaluationApi.kt
  15. +17 −0 src/main/kotlin/de/code_freak/codefreak/graphql/api/SubmissionApi.kt
  16. +18 −0 src/main/kotlin/de/code_freak/codefreak/graphql/api/TaskApi.kt
  17. +11 −0 src/main/kotlin/de/code_freak/codefreak/graphql/api/UserApi.kt
  18. +3 −0 src/main/kotlin/de/code_freak/codefreak/util/UtilExtensions.kt
  19. +12 −0 src/main/resources/application.yml
  20. +1 −1 src/test/kotlin/de/code_freak/codefreak/arch/ArchitectureTest.kt
  21. +6 −0 src/test/resources/application-test.yml
@@ -1,6 +1,6 @@
plugins {
id 'org.jetbrains.kotlin.plugin.jpa' version '1.3.21'
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'org.springframework.boot' version '2.2.0.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.3.21'
id 'org.jetbrains.kotlin.plugin.spring' version '1.3.21'
id 'com.diffplug.gradle.spotless' version '3.19.0'
@@ -59,6 +59,8 @@ dependencies {
implementation 'org.eclipse.jgit:org.eclipse.jgit:5.4.2.201908231537-r'
implementation 'org.eclipse.jgit:org.eclipse.jgit.archive:5.4.2.201908231537-r'
implementation 'org.liquibase:liquibase-core:3.6.3'
implementation 'com.graphql-java-kickstart:graphql-spring-boot-starter:5.10.0'
implementation 'com.expediagroup:graphql-kotlin-spring-server:1.2.2'
kapt 'org.springframework.boot:spring-boot-configuration-processor'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
@@ -169,3 +171,9 @@ sonarqube {
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml"
}
}

task generateGraphqlSchema(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = "de.code_freak.codefreak.graphql.SchemaPrinterKt"
args 'build/generated/graphql/schema.graphqls'
}
@@ -0,0 +1,18 @@
package de.code_freak.codefreak.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
import javax.servlet.Filter

@Configuration
class GraphQLConfiguration {

/**
* Enable JPA lazy loading for GraphQL DTOs
*/
@Bean
fun openFilter(): Filter {
return OpenEntityManagerInViewFilter()
}
}
@@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.BeanIds
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
@@ -27,11 +29,15 @@ class SecurityConfiguration : WebSecurityConfigurerAdapter() {
@Autowired(required = false)
var ldapUserDetailsContextMapper: LdapUserDetailsContextMapper? = null

@Bean(BeanIds.AUTHENTICATION_MANAGER)
override fun authenticationManagerBean(): AuthenticationManager = super.authenticationManagerBean()

override fun configure(http: HttpSecurity?) {
http
?.authorizeRequests()
?.requestMatchers(PathRequest.toStaticResources().atCommonLocations())?.permitAll()
?.antMatchers("/assets/**")?.permitAll()
?.antMatchers("/graphql/**")?.permitAll()
?.anyRequest()?.authenticated()
?.and()
?.formLogin()
@@ -40,6 +46,8 @@ class SecurityConfiguration : WebSecurityConfigurerAdapter() {
?.and()
?.logout()
?.permitAll()
?.and()
?.csrf()?.ignoringAntMatchers("/graphql")
}

@Bean
@@ -0,0 +1,44 @@
package de.code_freak.codefreak.graphql

import graphql.language.StringValue
import graphql.schema.Coercing
import graphql.schema.CoercingParseLiteralException
import graphql.schema.CoercingParseValueException
import graphql.schema.CoercingSerializeException
import java.time.Instant
import java.time.format.DateTimeParseException

class DateTimeConverter : Coercing<Instant, Instant> {

private fun parseLocalDateTime(input: Any?): Instant? {
if (input is Instant) return input
if (input !is String || input.isEmpty()) return null
return try {
Instant.parse(input)
} catch (e: DateTimeParseException) {
null
}
}

override fun parseValue(input: Any?): Instant {
return parseLocalDateTime(input) ?: throw CoercingParseValueException(
"Expected type 'Instant' but was '${input?.javaClass?.simpleName}'."
)
}

override fun parseLiteral(input: Any?): Instant {
if (input is StringValue) {
return parseLocalDateTime(input.value)
?: throw CoercingParseLiteralException("Unable to turn AST input into a 'Instant' : '$input'")
}
throw CoercingParseLiteralException(
"Expected AST type 'StringValue' but was '${input?.javaClass?.simpleName}'."
)
}

override fun serialize(dataFetcherResult: Any?): Instant {
return parseLocalDateTime(dataFetcherResult) ?: throw CoercingSerializeException(
"Expected type 'Instant' but was '${dataFetcherResult?.javaClass?.simpleName}'."
)
}
}
@@ -0,0 +1,34 @@
package de.code_freak.codefreak.graphql

import graphql.ErrorClassification
import graphql.ExceptionWhileDataFetching
import graphql.GraphQLError
import graphql.language.SourceLocation
import graphql.servlet.core.DefaultGraphQLErrorHandler
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.stereotype.Component

@Component
class ErrorHandler : DefaultGraphQLErrorHandler() {

private class CustomError(val ex: ExceptionWhileDataFetching, val customMessage: String, val code: String) : GraphQLError {
override fun getMessage(): String = customMessage
override fun getErrorType(): ErrorClassification = ex.errorType
override fun getLocations(): MutableList<SourceLocation> = ex.locations
override fun getExtensions(): MutableMap<String, Any> = mutableMapOf("errorCode" to code)
}

override fun processErrors(errors: MutableList<GraphQLError>?): MutableList<GraphQLError> {
val newErrors = errors?.map {
if (it is ExceptionWhileDataFetching) {
when (it.exception) {
is AccessDeniedException -> CustomError(it, "Access Denied", "403")
is BadCredentialsException -> CustomError(it, "Bad Credentials", "422")
else -> it
}
} else it
}
return super.processErrors(newErrors)
}
}
@@ -0,0 +1,22 @@
package de.code_freak.codefreak.graphql

import graphql.schema.GraphQLScalarType
import java.time.Instant
import java.util.UUID
import kotlin.reflect.KClassifier

object ScalarTypes {
private val scalars = mapOf(
UUID::class to GraphQLScalarType.Builder()
.name("ID")
.description("UUID")
.coercing(UuidConverter())
.build(),
Instant::class to GraphQLScalarType.Builder()
.name("DateTime")
.description("UTC date and time in ISO-8601 format")
.coercing(DateTimeConverter())
.build()
)
fun get(classifier: KClassifier) = scalars[classifier]
}
@@ -0,0 +1,13 @@
package de.code_freak.codefreak.graphql

import com.expediagroup.graphql.hooks.SchemaGeneratorHooks
import graphql.schema.GraphQLType
import org.springframework.stereotype.Component
import kotlin.reflect.KType

@Component
class SchemaGeneratorHooksImpl : SchemaGeneratorHooks {
override fun willGenerateGraphQLType(type: KType): GraphQLType? {
return type.classifier?.let { ScalarTypes.get(it) }
}
}
@@ -0,0 +1,41 @@
package de.code_freak.codefreak.graphql

import com.expediagroup.graphql.extensions.print
import com.expediagroup.graphql.spring.GraphQLConfigurationProperties
import com.expediagroup.graphql.spring.SchemaAutoConfiguration
import graphql.schema.GraphQLSchema
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.WebApplicationType
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.ComponentScan
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter

@ImportAutoConfiguration(SchemaAutoConfiguration::class)
@ComponentScan("de.code_freak.codefreak.graphql")
@EnableConfigurationProperties(GraphQLConfigurationProperties::class)
class SchemaPrinter : CommandLineRunner {

@Autowired
private lateinit var schema: GraphQLSchema

override fun run(vararg args: String?) {
require(args.isNotEmpty() && args[0] != null) { "Missing path!" }
val file = File(args[0]!!)
println("\nWriting schema to ${file.absolutePath}")
file.parentFile.mkdirs()
BufferedWriter(FileWriter(file)).use {
it.write(schema.print())
}
}
}

fun main(args: Array<String>) {
SpringApplicationBuilder(SchemaPrinter::class.java)
.web(WebApplicationType.NONE)
.run(*args)
}
@@ -0,0 +1,16 @@
package de.code_freak.codefreak.graphql

import de.code_freak.codefreak.service.BaseService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.stereotype.Component
import kotlin.reflect.KClass

@Component
class ServiceAccess {

@Autowired
private lateinit var applicationContext: ApplicationContext

fun <T : BaseService> getService(type: KClass<T>): T = applicationContext.getBean(type.java)
}
@@ -0,0 +1,40 @@
package de.code_freak.codefreak.graphql

import graphql.language.StringValue
import graphql.schema.Coercing
import graphql.schema.CoercingParseLiteralException
import graphql.schema.CoercingParseValueException
import java.util.UUID

class UuidConverter : Coercing<UUID, String> {

private fun parse(input: Any?): UUID? {
if (input is UUID) return input
if (input !is String || input.isEmpty()) return null
return try {
UUID.fromString(input)
} catch (e: IllegalArgumentException) {
null
}
}

override fun parseValue(input: Any?): UUID {
return parse(input) ?: throw CoercingParseValueException(
"Expected type 'UUID' but was '${input?.javaClass?.simpleName}'."
)
}

override fun parseLiteral(input: Any?): UUID {
if (input is StringValue) {
return parse(input.value)
?: throw CoercingParseLiteralException("Unable to turn AST input into a 'UUID' : '$input'")
}
throw CoercingParseLiteralException(
"Expected AST type 'StringValue' but was '${input?.javaClass?.simpleName}'."
)
}

override fun serialize(dataFetcherResult: Any?): String {
return dataFetcherResult.toString()
}
}
@@ -0,0 +1,25 @@
package de.code_freak.codefreak.graphql.api

import com.expediagroup.graphql.annotations.GraphQLID
import com.expediagroup.graphql.annotations.GraphQLIgnore
import com.expediagroup.graphql.annotations.GraphQLName
import de.code_freak.codefreak.entity.Answer
import de.code_freak.codefreak.graphql.ServiceAccess
import de.code_freak.codefreak.service.evaluation.EvaluationService
import de.code_freak.codefreak.util.orNull

@GraphQLName("Answer")
class AnswerDto(@GraphQLIgnore val entity: Answer, @GraphQLIgnore val serviceAccess: ServiceAccess) {

@GraphQLID
val id = entity.id
val submission by lazy { SubmissionDto(entity.submission, serviceAccess) }
val task by lazy { TaskDto(entity.task, serviceAccess) }

val latestEvaluation by lazy {
serviceAccess.getService(EvaluationService::class)
.getLatestEvaluation(id)
.map { EvaluationDto(it, serviceAccess) }
.orNull()
}
}
@@ -0,0 +1,56 @@
package de.code_freak.codefreak.graphql.api

import com.expediagroup.graphql.annotations.GraphQLID
import com.expediagroup.graphql.annotations.GraphQLIgnore
import com.expediagroup.graphql.annotations.GraphQLName
import com.expediagroup.graphql.spring.operations.Query
import de.code_freak.codefreak.auth.Authority
import de.code_freak.codefreak.auth.Role
import de.code_freak.codefreak.entity.Assignment
import de.code_freak.codefreak.graphql.ServiceAccess
import de.code_freak.codefreak.service.AssignmentService
import de.code_freak.codefreak.service.SubmissionService
import de.code_freak.codefreak.util.FrontendUtil
import graphql.schema.DataFetchingEnvironment
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.access.annotation.Secured
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@GraphQLName("Assignment")
class AssignmentDto(@GraphQLIgnore val entity: Assignment, @GraphQLIgnore val serviceAccess: ServiceAccess) {

@GraphQLID
val id = entity.id
val title = entity.title
val owner by lazy { UserDto(entity.owner) }
val deadline = entity.deadline
val closed = entity.closed
val tasks by lazy { entity.tasks.map { TaskDto(it, serviceAccess) } }

val submissions by lazy {
serviceAccess.getService(SubmissionService::class)
.findSubmissionsOfAssignment(id)
.map { SubmissionDto(it, serviceAccess) }
}
}

@Component
class AssignmentQuery : Query {

@Autowired
private lateinit var serviceAccess: ServiceAccess

@Transactional
@Secured(Authority.ROLE_STUDENT)
fun assignments(env: DataFetchingEnvironment): List<AssignmentDto> {
val assignmentService = serviceAccess.getService(AssignmentService::class)
val user = FrontendUtil.getCurrentUser()
val assignments = if (user.roles.contains(Role.TEACHER)) {
assignmentService.findAllAssignments()
} else {
assignmentService.findAllAssignmentsForUser(user.id)
}
return assignments.map { AssignmentDto(it, serviceAccess) }
}
}

0 comments on commit e9d5fd0

Please sign in to comment.
You can’t perform that action at this time.