Skip to content

Commit

Permalink
cleanup, api, docs, refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Niklas Kerkhoff committed Dec 11, 2024
1 parent 8ea30cf commit 5581b34
Show file tree
Hide file tree
Showing 94 changed files with 619 additions and 1,791 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package de.niklaskerkhoff.tutorassistantappservice.config
import de.niklaskerkhoff.tutorassistantappservice.lib.security.KeycloakJwtAuthenticationConverter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.core.env.Environment
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
Expand All @@ -18,10 +20,12 @@ import org.springframework.web.filter.CorsFilter
@EnableMethodSecurity
class SecurityConfig {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
fun securityFilterChain(http: HttpSecurity, env: Environment): SecurityFilterChain {
http {
csrf { disable() }
// cors { }
if (!env.activeProfiles.contains("prod")) {
cors { }
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
Expand All @@ -37,22 +41,23 @@ class SecurityConfig {
return http.build()
}

// @Bean
// fun corsFilter(): CorsFilter {
// val source = UrlBasedCorsConfigurationSource()
// val config = CorsConfiguration()
// config.allowCredentials = true
// config.allowedOrigins = listOf(
// "http://localhost:5173",
// "http://localhost:5174",
// )
// config.allowedHeaders = listOf(
// "Origin", "Content-Type", "Accept", "Authorization",
// "Access-Control-Allow-Origin"
// )
// config.allowedMethods =
// listOf("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
// source.registerCorsConfiguration("/**", config)
// return CorsFilter(source)
// }
@Bean
@Profile("!prod")
fun corsFilter(): CorsFilter {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration()
config.allowCredentials = true
config.allowedOrigins = listOf(
"http://localhost:5173",
"http://localhost:5174",
)
config.allowedHeaders = listOf(
"Origin", "Content-Type", "Accept", "Authorization",
"Access-Control-Allow-Origin"
)
config.allowedMethods =
listOf("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")
source.registerCorsConfiguration("/**", config)
return CorsFilter(source)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class WebClientConfig {
@Bean
fun webClient(): WebClient {
return WebClient.builder()
.codecs { it.defaultCodecs().maxInMemorySize(10 * 1024 * 1024) }
.codecs { it.defaultCodecs().maxInMemorySize(10 * 1024 * 1024) } // 10MB
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.app_components

import de.niklaskerkhoff.tutorassistantappservice.lib.logging.Logger

abstract class AppController : Logger {
}
/**
* Abstract class every controller should inherit from.
* Provides base functionality and ensures extensibility.
*/
abstract class AppController : Logger
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.app_components

import de.niklaskerkhoff.tutorassistantappservice.lib.logging.Logger

abstract class AppService : Logger {
}
/**
* Abstract class every service should inherit from.
* Provides base functionality and ensures extensibility.
*/
abstract class AppService : Logger
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,50 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.Instant
import java.util.*

/**
* Abstract class every entity should inherit from.
* Provides entity-id and JPA-Auditing.
*/
@Entity
@EntityListeners(AuditingEntityListener::class)
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
abstract class AppEntity {
/**
* ID of the entity. This property is used as the primary key.
* This property is set by JPA-Auditing. It must not be modified manually!
*/
@Id
@GeneratedValue(strategy = GenerationType.UUID)
open val id: UUID? = null

// @Version
// open val version = 0

/**
* Date, the entity is created in the database.
* This property is set by JPA-Auditing. It must not be modified manually!
*/
@CreatedDate
open var createdDate: Instant? = null
protected set

/**
* Date, the entity is updated in the database lastly.
* This property is set by JPA-Auditing. It must not be modified manually!
*/
@LastModifiedDate
open var lastModifiedDate: Instant? = null
protected set

/**
* ID of the user whose request created the entity in the database.
* This property is set by JPA-Auditing. It must not be modified manually!
*/
@CreatedBy
open var createdBy: String? = null
protected set

/**
* ID of the user whose request updated the entity in the database lastly.
* This property is set by JPA-Auditing. It must not be modified manually!
*/
@LastModifiedBy
open var lastModifiedBy: String? = null
protected set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.repository.NoRepositoryBean
import java.util.*

/**
* Interface from which every entity's repository should inherit from.
* Leads to concise implementation and can provide additional methods through extension functions.
*/
@NoRepositoryBean
interface AppEntityRepo<E> : JpaRepository<E, UUID>
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.entities
import org.springframework.data.repository.findByIdOrNull
import java.util.*

/**
* @param id of the Entity which is searched.
* @returns Entity of the particular inherited AppEntityRepo with the given id.
* @throws IllegalArgumentException iff the id is not found.
*/
fun <T> AppEntityRepo<T>.findByIdOrThrow(id: UUID) = findByIdOrNull(id) ?: throw IllegalArgumentException(id.toString())
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.exceptions
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException

/**
* This exception acts as default exception if something goes wrong.
*
* @param message to specify the error.
*/
class BadRequestException(message: String) : ResponseStatusException(HttpStatus.BAD_REQUEST, message)
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,34 @@ import jakarta.persistence.Embeddable
import jakarta.persistence.Embedded
import jakarta.persistence.Entity

/**
* Instances of these class are stored in the database as references to the files stored in the file store.
*/
@Entity
class FileStoreFileReference(
/**
* @see FileStoreAssignment
*/
@Embedded val assignment: FileStoreAssignment,
/**
* @see FileStoreUpload
*/
@Embedded val upload: FileStoreUpload,

/**
* URL under which the file ist accessible.
*/
val storeUrl: String,

/**
* For using a different name than the original file name.
*/
val displayName: String = upload.name
) : AppEntity()

/**
* Result of the file store for an assignment.
*/
@Embeddable
data class FileStoreAssignment(
val fid: String,
Expand All @@ -21,13 +41,19 @@ data class FileStoreAssignment(
val count: Int,
)

/**
* Result of the file store for an upload.
*/
@Embeddable
data class FileStoreUpload(
val name: String,
val size: Long,
val eTag: String,
)

/**
* Result of the file store for a deletion.
*/
data class FileStoreDelete(
val size: Long,
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import org.springframework.web.multipart.MultipartFile
import org.springframework.web.reactive.function.client.WebClient
import java.util.*

/**
* Provides file store interaction.
*/
@Service
class FileStoreService(
private val webClient: WebClient,
Expand All @@ -20,8 +23,17 @@ class FileStoreService(
@Value("\${app.seaweedfs.master-url}")
private lateinit var masterUrl: String

/**
* @returns all stored FileStoreFileReferences.
*/
fun listFiles(): List<FileStoreFileReference> = fileStoreFileReferenceDefaultRepo.findAll()

/**
* Loads the file content.
*
* @param id of the FileStoreFileReference whose file content shall be loaded.
* @returns Pair of the FileStoreFileReference and content as InputStreamResource.
*/
fun getFileById(id: UUID): Pair<FileStoreFileReference, InputStreamResource> {
val fileReference = fileStoreFileReferenceDefaultRepo.findByIdOrThrow(id)

Expand All @@ -34,6 +46,14 @@ class FileStoreService(
return Pair(fileReference, InputStreamResource(fileBytes.inputStream()))
}

/**
* Assigns and uploads a file.
*
* @see assign
* @see upload
* @param file to be uploaded.
* @param displayName name of the file under which it is uploaded so the original file name must not be changed.
*/
fun assignAndUpload(file: MultipartFile, displayName: String? = null): FileStoreFileReference {
val assignment = assign()
val storeUrl = "http://${assignment.publicUrl}/${assignment.fid}"
Expand All @@ -44,6 +64,11 @@ class FileStoreService(
return fileStoreFileReferenceDefaultRepo.save(fileReference)
}

/**
* Assigns a storage location for a new file.
*
* @returns an FileStoreAssignment containing a reference (url) to the storage location.
*/
fun assign(): FileStoreAssignment {
return webClient.get()
.uri(masterUrl)
Expand All @@ -52,6 +77,13 @@ class FileStoreService(
.block() ?: throw EmptyResponseBodyException()
}

/**
* Uploads a file to the file store.
*
* @param storeUrl to which the file shall be uploaded.
* @param file to be uploaded.
* @returns FileStoreUpload as the result of the file store.
*/
fun upload(storeUrl: String, file: MultipartFile): FileStoreUpload {
val body: MultiValueMap<String, Any> = LinkedMultiValueMap()
body.add("file", file.resource)
Expand All @@ -65,6 +97,12 @@ class FileStoreService(
.block() ?: throw EmptyResponseBodyException()
}

/**
* Deletes a file from a file from the file store and the FileStoreFileReference from the database.
*
* @param id of the FileStoreFileReference.
* @returns FileStoreDelete as the result of the file store.
*/
fun deleteById(id: UUID): FileStoreDelete {
val fileReference = fileStoreFileReferenceDefaultRepo.findByIdOrThrow(id)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.logging
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
* Provides concise logging.
*/
interface Logger {
val log: Logger get() = LoggerFactory.getLogger(javaClass)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import jakarta.servlet.http.HttpServletResponse
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

/**
* OncePerRequestFilter for logging base information about incoming requests and outgoing responses.
*/
@Component
class RequestLoggingFilter : OncePerRequestFilter(), Logger {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter


/**
* Provides the claims of a Jwt generated by Keycloak to Spring Security.
*/
class KeycloakJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {

override fun convert(jwt: Jwt): AbstractAuthenticationToken? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.security
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken

fun JwtAuthenticationToken.hasAuthority(authority: String) = authorities.contains(SimpleGrantedAuthority(authority))
/**
* @param authority which is required.
* @returns true if this JwtAuthenticationToken has a given authority, false otherwise.
*/
fun JwtAuthenticationToken.hasAuthority(authority: String) = authorities.contains(SimpleGrantedAuthority(authority))
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package de.niklaskerkhoff.tutorassistantappservice.lib.webclient

/**
* Exception to be thrown when the response body of a request is empty.
*/
class EmptyResponseBodyException : RuntimeException("Response body must not be null")
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.niklaskerkhoff.tutorassistantappservice.modules.calendar

import de.niklaskerkhoff.tutorassistantappservice.modules.calendar.entities.Calendar
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.*

Expand All @@ -9,14 +8,16 @@ import org.springframework.web.bind.annotation.*
class CalendarController(
private val calendarService: CalendarService
) {
/**
* @see CalendarService.getCalendar
*/
@GetMapping
fun getInfo(@RequestParam currentDate: String, @RequestParam currentTitle: String): List<CalendarFrontendData> =
fun getCalendar(@RequestParam currentDate: String, @RequestParam currentTitle: String): List<CalendarFrontendData> =
calendarService.getCalendar(currentDate, currentTitle)

@GetMapping("all")
@PreAuthorize("hasRole('document-manager')")
fun getAllInfos(): List<Calendar> = calendarService.getAllCalendars()

/**
* @see CalendarService.loadNewCalendar
*/
@PostMapping
@PreAuthorize("hasRole('document-manager')")
fun reloadInfo() = calendarService.loadNewCalendar()
Expand Down
Loading

0 comments on commit 5581b34

Please sign in to comment.