develop #22
|
@ -22,7 +22,7 @@ repositories {
|
|||
mavenCentral()
|
||||
|
||||
maven {
|
||||
url = uri("https://git.fyloz.dev/api/v4/projects/40/packages/maven")
|
||||
url = uri("https://archiva.fyloz.dev/repository/internal")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ dependencies {
|
|||
implementation("io.jsonwebtoken:jjwt:0.9.1")
|
||||
implementation("org.apache.poi:poi-ooxml:4.1.0")
|
||||
implementation("org.apache.pdfbox:pdfbox:2.0.4")
|
||||
implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2")
|
||||
implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2.1")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
|
||||
implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}")
|
||||
|
@ -46,10 +46,10 @@ dependencies {
|
|||
implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}")
|
||||
|
||||
testImplementation("org.springframework:spring-test:5.1.6.RELEASE")
|
||||
testImplementation("org.mockito:mockito-inline:3.11.2")
|
||||
testImplementation("org.mockito:mockito-inline:3.12.4")
|
||||
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.2")
|
||||
testImplementation("io.mockk:mockk:1.10.6")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.2")
|
||||
testImplementation("io.mockk:mockk:1.12.0")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
|
||||
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}")
|
||||
|
|
|
@ -100,19 +100,13 @@ enum class ConfigurationType(
|
|||
val secure: Boolean = false
|
||||
) {
|
||||
INSTANCE_NAME("instance.name", defaultContent = "Color Recipes Explorer", public = true),
|
||||
INSTANCE_LOGO_PATH("instance.logo.path", defaultContent = "images/logo", public = true),
|
||||
INSTANCE_ICON_PATH("instance.icon.path", defaultContent = "images/icon", public = true),
|
||||
INSTANCE_LOGO_SET("instance.logo.set", defaultContent = false, public = true),
|
||||
INSTANCE_ICON_SET("instance.icon.set", defaultContent = false, public = true),
|
||||
INSTANCE_URL("instance.url", "http://localhost:9090", public = true),
|
||||
|
||||
DATABASE_URL("database.url", defaultContent = "mysql://localhost/cre", file = true, requireRestart = true),
|
||||
DATABASE_USER("database.user", defaultContent = "cre", file = true, requireRestart = true),
|
||||
DATABASE_PASSWORD(
|
||||
"database.password",
|
||||
defaultContent = "asecurepassword",
|
||||
file = true,
|
||||
requireRestart = true,
|
||||
secure = true
|
||||
),
|
||||
DATABASE_PASSWORD("database.password", defaultContent = "asecurepassword", file = true, requireRestart = true, secure = true),
|
||||
DATABASE_SUPPORTED_VERSION("database.version.supported", computed = true),
|
||||
|
||||
RECIPE_APPROBATION_EXPIRATION("recipe.approbation.expiration", defaultContent = 4.months),
|
||||
|
|
|
@ -7,6 +7,7 @@ import dev.fyloz.colorrecipesexplorer.model.account.Permission
|
|||
import dev.fyloz.colorrecipesexplorer.model.account.toAuthority
|
||||
import dev.fyloz.colorrecipesexplorer.restartApplication
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
@ -33,17 +34,35 @@ class ConfigurationController(val configurationService: ConfigurationService) {
|
|||
configurationService.set(configurations)
|
||||
}
|
||||
|
||||
@PutMapping("image")
|
||||
@PreAuthorize("hasAuthority('ADMIN')")
|
||||
fun setImage(@RequestParam @NotBlank key: String, @RequestParam @NotBlank image: MultipartFile) = noContent {
|
||||
configurationService.set(ConfigurationImageDto(key, image))
|
||||
}
|
||||
|
||||
@PostMapping("restart")
|
||||
@PreAuthorize("hasAuthority('ADMIN')")
|
||||
fun restart() = noContent {
|
||||
restartApplication()
|
||||
}
|
||||
|
||||
// Icon
|
||||
|
||||
@GetMapping("icon")
|
||||
fun getIcon() =
|
||||
okFile(configurationService.getConfiguredIcon(), MediaType.IMAGE_PNG_VALUE)
|
||||
|
||||
@PutMapping("icon")
|
||||
@PreAuthorize("hasAuthority('ADMIN')")
|
||||
fun setIcon(@RequestParam icon: MultipartFile) = noContent {
|
||||
configurationService.setConfiguredIcon(icon)
|
||||
}
|
||||
|
||||
// Logo
|
||||
|
||||
@GetMapping("logo")
|
||||
fun getLogo() =
|
||||
okFile(configurationService.getConfiguredLogo(), MediaType.IMAGE_PNG_VALUE)
|
||||
|
||||
@PutMapping("logo")
|
||||
@PreAuthorize("hasAuthority('ADMIN')")
|
||||
fun setLogo(@RequestParam logo: MultipartFile) = noContent {
|
||||
configurationService.setConfiguredLogo(logo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Authentication?.hasAuthority(configuration: ConfigurationBase) = when {
|
||||
|
|
|
@ -2,8 +2,7 @@ package dev.fyloz.colorrecipesexplorer.rest
|
|||
|
||||
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
|
||||
import dev.fyloz.colorrecipesexplorer.service.FileService
|
||||
import org.springframework.core.io.ByteArrayResource
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
|
@ -12,26 +11,18 @@ import org.springframework.web.multipart.MultipartFile
|
|||
import java.net.URI
|
||||
|
||||
const val FILE_CONTROLLER_PATH = "/api/file"
|
||||
private const val DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE
|
||||
|
||||
@RestController
|
||||
@RequestMapping(FILE_CONTROLLER_PATH)
|
||||
class FileController(
|
||||
private val fileService: FileService,
|
||||
private val fileService: WriteableFileService,
|
||||
private val configService: ConfigurationService
|
||||
) {
|
||||
@GetMapping(produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
|
||||
fun upload(
|
||||
@RequestParam path: String,
|
||||
@RequestParam(required = false) mediaType: String?
|
||||
): ResponseEntity<ByteArrayResource> {
|
||||
val file = fileService.read(path)
|
||||
return ResponseEntity.ok()
|
||||
.header("Content-Disposition", "filename=${getFileNameFromPath(path)}")
|
||||
.contentLength(file.contentLength())
|
||||
.contentType(MediaType.parseMediaType(mediaType ?: DEFAULT_MEDIA_TYPE))
|
||||
.body(file)
|
||||
}
|
||||
) = okFile(fileService.read(path), mediaType)
|
||||
|
||||
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorize("hasAnyAuthority('WRITE_FILE')")
|
||||
|
@ -46,17 +37,13 @@ class FileController(
|
|||
|
||||
@DeleteMapping
|
||||
@PreAuthorize("hasAnyAuthority('WRITE_FILE')")
|
||||
fun delete(@RequestParam path: String): ResponseEntity<Void> {
|
||||
return noContent {
|
||||
fun delete(@RequestParam path: String): ResponseEntity<Void> =
|
||||
noContent {
|
||||
fileService.delete(path)
|
||||
}
|
||||
}
|
||||
|
||||
private fun created(path: String): ResponseEntity<Void> =
|
||||
ResponseEntity
|
||||
.created(URI.create("${configService.get(ConfigurationType.INSTANCE_URL)}$FILE_CONTROLLER_PATH?path=$path"))
|
||||
.build()
|
||||
|
||||
private fun getFileNameFromPath(path: String) =
|
||||
path.split("/").last()
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ package dev.fyloz.colorrecipesexplorer.rest
|
|||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||
import dev.fyloz.colorrecipesexplorer.model.Model
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import java.net.URI
|
||||
|
||||
const val DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE
|
||||
lateinit var CRE_PROPERTIES: CreProperties
|
||||
|
||||
/** Creates a HTTP OK [ResponseEntity] from the given [body]. */
|
||||
|
@ -24,6 +26,14 @@ fun ok(action: () -> Unit): ResponseEntity<Void> {
|
|||
return ResponseEntity.ok().build()
|
||||
}
|
||||
|
||||
/** Creates a HTTP OK [ResponseEntity] for the given [file], with the given [mediaType]. */
|
||||
fun okFile(file: Resource, mediaType: String? = null): ResponseEntity<Resource> =
|
||||
ResponseEntity.ok()
|
||||
.header("Content-Disposition", "filename=${file.filename}")
|
||||
.contentLength(file.contentLength())
|
||||
.contentType(MediaType.parseMediaType(mediaType ?: DEFAULT_MEDIA_TYPE))
|
||||
.body(file)
|
||||
|
||||
/** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */
|
||||
fun <T : Model> created(controllerPath: String, body: T): ResponseEntity<T> =
|
||||
created(controllerPath, body, body.id!!)
|
||||
|
@ -63,3 +73,6 @@ fun httpHeaders(
|
|||
|
||||
op()
|
||||
}
|
||||
|
||||
fun getFileNameFromPath(path: String) =
|
||||
path.split("/").last()
|
||||
|
|
|
@ -5,7 +5,7 @@ import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKitSaveDto
|
|||
import dev.fyloz.colorrecipesexplorer.model.touchupkit.TouchUpKitUpdateDto
|
||||
import dev.fyloz.colorrecipesexplorer.service.TouchUpKitService
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.core.io.ByteArrayResource
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
|
@ -57,7 +57,7 @@ class TouchUpKitController(
|
|||
}
|
||||
|
||||
@GetMapping("pdf")
|
||||
fun getJobPdf(@RequestParam project: String): ResponseEntity<ByteArrayResource> {
|
||||
fun getJobPdf(@RequestParam project: String): ResponseEntity<Resource> {
|
||||
with(touchUpKitService.generateJobPdfResource(project)) {
|
||||
return ResponseEntity.ok()
|
||||
.header("Content-Disposition", "filename=TouchUpKit_$project.pdf")
|
||||
|
|
|
@ -4,6 +4,7 @@ import dev.fyloz.colorrecipesexplorer.model.*
|
|||
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
||||
import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import io.jsonwebtoken.lang.Assert
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.context.annotation.Profile
|
||||
|
@ -39,7 +40,7 @@ class MaterialServiceImpl(
|
|||
val recipeService: RecipeService,
|
||||
val mixService: MixService,
|
||||
@Lazy val materialTypeService: MaterialTypeService,
|
||||
val fileService: FileService,
|
||||
val fileService: WriteableFileService,
|
||||
val configService: ConfigurationService
|
||||
) :
|
||||
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialOutputDto, MaterialRepository>(
|
||||
|
|
|
@ -5,6 +5,7 @@ import dev.fyloz.colorrecipesexplorer.model.account.Group
|
|||
import dev.fyloz.colorrecipesexplorer.model.validation.or
|
||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.setAll
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.context.annotation.Profile
|
||||
|
@ -222,7 +223,7 @@ const val RECIPE_IMAGE_EXTENSION = ".jpg"
|
|||
@Service
|
||||
@Profile("!emergency")
|
||||
class RecipeImageServiceImpl(
|
||||
val fileService: FileService
|
||||
val fileService: WriteableFileService
|
||||
) : RecipeImageService {
|
||||
override fun getAllImages(recipe: Recipe): Set<String> {
|
||||
val recipeDirectory = recipe.getDirectory()
|
||||
|
|
|
@ -5,9 +5,12 @@ import dev.fyloz.colorrecipesexplorer.model.touchupkit.*
|
|||
import dev.fyloz.colorrecipesexplorer.repository.TouchUpKitRepository
|
||||
import dev.fyloz.colorrecipesexplorer.rest.TOUCH_UP_KIT_CONTROLLER_PATH
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.*
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.core.io.ByteArrayResource
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDate
|
||||
import java.time.Period
|
||||
|
@ -32,7 +35,7 @@ interface TouchUpKitService :
|
|||
* If TOUCH_UP_KIT_CACHE_PDF is enabled and a file exists for the job, its content will be returned.
|
||||
* If caching is enabled but no file exists for the job, the generated ByteArrayResource will be cached on the disk.
|
||||
*/
|
||||
fun generateJobPdfResource(job: String): ByteArrayResource
|
||||
fun generateJobPdfResource(job: String): Resource
|
||||
|
||||
/** Writes the given [document] to the [FileService] if TOUCH_UP_KIT_CACHE_PDF is enabled. */
|
||||
fun String.cachePdfDocument(document: PdfDocument)
|
||||
|
@ -41,7 +44,7 @@ interface TouchUpKitService :
|
|||
@Service
|
||||
@Profile("!emergency")
|
||||
class TouchUpKitServiceImpl(
|
||||
private val fileService: FileService,
|
||||
private val fileService: WriteableFileService,
|
||||
private val configService: ConfigurationService,
|
||||
touchUpKitRepository: TouchUpKitRepository
|
||||
) : AbstractExternalModelService<TouchUpKit, TouchUpKitSaveDto, TouchUpKitUpdateDto, TouchUpKitOutputDto, TouchUpKitRepository>(
|
||||
|
@ -120,7 +123,7 @@ class TouchUpKitServiceImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun generateJobPdfResource(job: String): ByteArrayResource {
|
||||
override fun generateJobPdfResource(job: String): Resource {
|
||||
if (cacheGeneratedFiles) {
|
||||
with(job.pdfDocumentPath()) {
|
||||
if (fileService.exists(this)) {
|
||||
|
|
|
@ -2,13 +2,16 @@ package dev.fyloz.colorrecipesexplorer.service.config
|
|||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreSecurityProperties
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.service.FileService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.ResourceFileService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.decrypt
|
||||
import dev.fyloz.colorrecipesexplorer.utils.encrypt
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
interface ConfigurationService {
|
||||
/** Gets all set configurations. */
|
||||
|
@ -35,6 +38,12 @@ interface ConfigurationService {
|
|||
/** Gets the content of the secure configuration with the given [type]. Should not be accessible to the users. */
|
||||
fun getSecure(type: ConfigurationType): String
|
||||
|
||||
/** Gets the app's icon. */
|
||||
fun getConfiguredIcon(): Resource
|
||||
|
||||
/** Gets the app's logo. */
|
||||
fun getConfiguredLogo(): Resource
|
||||
|
||||
/** Sets the content of each configuration in the given [configurations] list. */
|
||||
fun set(configurations: List<ConfigurationDto>)
|
||||
|
||||
|
@ -47,20 +56,26 @@ interface ConfigurationService {
|
|||
/** Sets the content given [configuration]. */
|
||||
fun set(configuration: Configuration)
|
||||
|
||||
/** Sets the content of the configuration matching the given [configuration] with a given image. */
|
||||
fun set(configuration: ConfigurationImageDto)
|
||||
/** Sets the app's icon. */
|
||||
fun setConfiguredIcon(icon: MultipartFile)
|
||||
|
||||
/** Sets the app's logo. */
|
||||
fun setConfiguredLogo(logo: MultipartFile)
|
||||
|
||||
/** Initialize the properties matching the given [predicate]. */
|
||||
fun initializeProperties(predicate: (ConfigurationType) -> Boolean)
|
||||
}
|
||||
|
||||
const val CONFIGURATION_LOGO_RESOURCE_PATH = "images/logo.png"
|
||||
const val CONFIGURATION_LOGO_FILE_PATH = "images/logo"
|
||||
const val CONFIGURATION_ICON_RESOURCE_PATH = "images/icon.png"
|
||||
const val CONFIGURATION_ICON_FILE_PATH = "images/icon"
|
||||
const val CONFIGURATION_FORMATTED_LIST_DELIMITER = ';'
|
||||
|
||||
@Service("configurationService")
|
||||
class ConfigurationServiceImpl(
|
||||
@Lazy private val fileService: FileService,
|
||||
@Lazy private val fileService: WriteableFileService,
|
||||
private val resourceFileService: ResourceFileService,
|
||||
private val configurationSource: ConfigurationSource,
|
||||
private val securityProperties: CreSecurityProperties,
|
||||
private val logger: Logger
|
||||
|
@ -121,6 +136,29 @@ class ConfigurationServiceImpl(
|
|||
return decryptConfiguration(configuration).content
|
||||
}
|
||||
|
||||
override fun getConfiguredIcon() =
|
||||
getConfiguredImage(
|
||||
type = ConfigurationType.INSTANCE_ICON_SET,
|
||||
filePath = CONFIGURATION_ICON_FILE_PATH,
|
||||
resourcePath = CONFIGURATION_ICON_RESOURCE_PATH
|
||||
)
|
||||
|
||||
override fun getConfiguredLogo() =
|
||||
getConfiguredImage(
|
||||
type = ConfigurationType.INSTANCE_LOGO_SET,
|
||||
filePath = CONFIGURATION_LOGO_FILE_PATH,
|
||||
resourcePath = CONFIGURATION_LOGO_RESOURCE_PATH
|
||||
)
|
||||
|
||||
private fun getConfiguredImage(type: ConfigurationType, filePath: String, resourcePath: String) =
|
||||
with(get(type) as Configuration) {
|
||||
if (this.content == true.toString()) {
|
||||
fileService.read(filePath)
|
||||
} else {
|
||||
resourceFileService.read(resourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
override fun set(configurations: List<ConfigurationDto>) {
|
||||
configurationSource.set(
|
||||
configurations
|
||||
|
@ -136,14 +174,15 @@ class ConfigurationServiceImpl(
|
|||
configurationSource.set(encryptConfigurationIfSecure(configuration))
|
||||
}
|
||||
|
||||
override fun set(configuration: ConfigurationImageDto) {
|
||||
val filePath = when (val configurationType = configuration.key.toConfigurationType()) {
|
||||
ConfigurationType.INSTANCE_LOGO_PATH -> CONFIGURATION_LOGO_FILE_PATH
|
||||
ConfigurationType.INSTANCE_ICON_PATH -> CONFIGURATION_ICON_FILE_PATH
|
||||
else -> throw InvalidImageConfigurationException(configurationType)
|
||||
}
|
||||
override fun setConfiguredIcon(icon: MultipartFile) =
|
||||
setConfiguredImage(icon, CONFIGURATION_ICON_FILE_PATH, ConfigurationType.INSTANCE_ICON_SET)
|
||||
|
||||
fileService.write(configuration.image, filePath, true)
|
||||
override fun setConfiguredLogo(logo: MultipartFile) =
|
||||
setConfiguredImage(logo, CONFIGURATION_LOGO_FILE_PATH, ConfigurationType.INSTANCE_LOGO_SET)
|
||||
|
||||
private fun setConfiguredImage(image: MultipartFile, path: String, type: ConfigurationType) {
|
||||
fileService.write(image, path, true)
|
||||
set(configuration(type, content = true.toString()))
|
||||
}
|
||||
|
||||
override fun initializeProperties(predicate: (ConfigurationType) -> Boolean) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import dev.fyloz.colorrecipesexplorer.model.Configuration
|
|||
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
||||
import dev.fyloz.colorrecipesexplorer.model.configuration
|
||||
import dev.fyloz.colorrecipesexplorer.repository.ConfigurationRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.create
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.create
|
||||
import dev.fyloz.colorrecipesexplorer.utils.excludeAll
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.boot.info.BuildProperties
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package dev.fyloz.colorrecipesexplorer.service
|
||||
package dev.fyloz.colorrecipesexplorer.service.files
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.core.io.ByteArrayResource
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
@ -23,8 +24,13 @@ interface FileService {
|
|||
fun exists(path: String): Boolean
|
||||
|
||||
/** Reads the file at the given [path]. */
|
||||
fun read(path: String): ByteArrayResource
|
||||
fun read(path: String): Resource
|
||||
|
||||
/** Completes the path of the given [String] by adding the working directory. */
|
||||
fun String.fullPath(): FilePath
|
||||
}
|
||||
|
||||
interface WriteableFileService : FileService {
|
||||
/** Creates a file at the given [path]. */
|
||||
fun create(path: String)
|
||||
|
||||
|
@ -36,16 +42,13 @@ interface FileService {
|
|||
|
||||
/** Deletes the file at the given [path]. */
|
||||
fun delete(path: String)
|
||||
|
||||
/** Completes the path of the given [String] by adding the working directory. */
|
||||
fun String.fullPath(): FilePath
|
||||
}
|
||||
|
||||
@Service
|
||||
class FileServiceImpl(
|
||||
private val creProperties: CreProperties,
|
||||
private val logger: Logger
|
||||
) : FileService {
|
||||
) : WriteableFileService {
|
||||
override fun exists(path: String) = withFileAt(path.fullPath()) {
|
||||
this.exists() && this.isFile
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package dev.fyloz.colorrecipesexplorer.service.files
|
||||
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.core.io.ResourceLoader
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class ResourceFileService(
|
||||
private val resourceLoader: ResourceLoader
|
||||
) : FileService {
|
||||
override fun exists(path: String) =
|
||||
path.fullPath().resource.exists()
|
||||
|
||||
override fun read(path: String): Resource =
|
||||
path.fullPath().resource.also {
|
||||
if (!it.exists()) {
|
||||
throw FileNotFoundException(path)
|
||||
}
|
||||
}
|
||||
|
||||
override fun String.fullPath() =
|
||||
FilePath("classpath:${this}")
|
||||
|
||||
val FilePath.resource: Resource
|
||||
get() = resourceLoader.getResource(this.path)
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
|
@ -5,23 +5,35 @@ import dev.fyloz.colorrecipesexplorer.model.*
|
|||
import dev.fyloz.colorrecipesexplorer.service.config.CONFIGURATION_FORMATTED_LIST_DELIMITER
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationServiceImpl
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationSource
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.ResourceFileService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.encrypt
|
||||
import io.mockk.*
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.UnsupportedOperationException
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ConfigurationServiceTest {
|
||||
private val fileService = mockk<FileService>()
|
||||
private val fileService = mockk<WriteableFileService>()
|
||||
private val resourceFileService = mockk<ResourceFileService>()
|
||||
private val configurationSource = mockk<ConfigurationSource>()
|
||||
private val securityProperties = mockk<CreSecurityProperties> {
|
||||
every { configSalt } returns "d32270943af7e1cc"
|
||||
}
|
||||
private val service = spyk(ConfigurationServiceImpl(fileService, configurationSource, securityProperties, mockk()))
|
||||
private val service = spyk(
|
||||
ConfigurationServiceImpl(
|
||||
fileService,
|
||||
resourceFileService,
|
||||
configurationSource,
|
||||
securityProperties,
|
||||
mockk()
|
||||
)
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun afterEach() {
|
||||
|
@ -49,8 +61,8 @@ class ConfigurationServiceTest {
|
|||
fun `getAll() only returns set configurations`() {
|
||||
val unsetConfigurationTypes = listOf(
|
||||
ConfigurationType.INSTANCE_NAME,
|
||||
ConfigurationType.INSTANCE_LOGO_PATH,
|
||||
ConfigurationType.INSTANCE_ICON_PATH
|
||||
ConfigurationType.INSTANCE_LOGO_SET,
|
||||
ConfigurationType.INSTANCE_ICON_SET
|
||||
)
|
||||
|
||||
every { service.get(match<ConfigurationType> { it in unsetConfigurationTypes }) } answers {
|
||||
|
@ -82,8 +94,8 @@ class ConfigurationServiceTest {
|
|||
fun `getAll() only includes configurations matching the formatted formatted key list`() {
|
||||
val configurationTypes = listOf(
|
||||
ConfigurationType.INSTANCE_NAME,
|
||||
ConfigurationType.INSTANCE_LOGO_PATH,
|
||||
ConfigurationType.INSTANCE_ICON_PATH
|
||||
ConfigurationType.INSTANCE_LOGO_SET,
|
||||
ConfigurationType.INSTANCE_ICON_SET
|
||||
)
|
||||
val formattedKeyList = configurationTypes
|
||||
.map { it.key }
|
||||
|
@ -113,7 +125,7 @@ class ConfigurationServiceTest {
|
|||
|
||||
@Test
|
||||
fun `get(key) calls get() with the ConfigurationType matching the given key`() {
|
||||
val type = ConfigurationType.INSTANCE_ICON_PATH
|
||||
val type = ConfigurationType.INSTANCE_ICON_SET
|
||||
val key = type.key
|
||||
|
||||
every { service.get(type) } answers {
|
||||
|
@ -132,7 +144,7 @@ class ConfigurationServiceTest {
|
|||
|
||||
@Test
|
||||
fun `get(type) gets the configuration in the ConfigurationSource`() {
|
||||
val type = ConfigurationType.INSTANCE_ICON_PATH
|
||||
val type = ConfigurationType.INSTANCE_ICON_SET
|
||||
val configuration = configuration(type = type)
|
||||
|
||||
every { configurationSource.get(type) } returns configuration
|
||||
|
@ -144,7 +156,7 @@ class ConfigurationServiceTest {
|
|||
|
||||
@Test
|
||||
fun `get(type) throws ConfigurationNotSetException when the given ConfigurationType has no set configuration`() {
|
||||
val type = ConfigurationType.INSTANCE_ICON_PATH
|
||||
val type = ConfigurationType.INSTANCE_ICON_SET
|
||||
|
||||
every { configurationSource.get(type) } returns null
|
||||
|
||||
|
@ -228,6 +240,57 @@ class ConfigurationServiceTest {
|
|||
assertThrows<UnsupportedOperationException> { service.getSecure(type) }
|
||||
}
|
||||
|
||||
private fun getConfiguredImageTest(
|
||||
configurationType: ConfigurationType,
|
||||
imageSet: Boolean,
|
||||
test: (Resource) -> Unit
|
||||
) {
|
||||
val resource = mockk<Resource>()
|
||||
val configuration = configuration(configurationType, imageSet.toString())
|
||||
val imageService = if (imageSet) fileService else resourceFileService
|
||||
|
||||
every { service.get(configurationType) } returns configuration
|
||||
every { imageService.read(any()) } returns resource
|
||||
|
||||
test(resource)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getConfiguredIcon() gets icon from resources when INSTANCE_ICON_SET configuration is false`() {
|
||||
getConfiguredImageTest(ConfigurationType.INSTANCE_ICON_SET, false) { resource ->
|
||||
val found = service.getConfiguredIcon()
|
||||
|
||||
assertEquals(resource, found)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getConfiguredIcon() gets icon from files when INSTANCE_ICON_SET configuration is true`() {
|
||||
getConfiguredImageTest(ConfigurationType.INSTANCE_ICON_SET, true) { resource ->
|
||||
val found = service.getConfiguredIcon()
|
||||
|
||||
assertEquals(resource, found)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getConfiguredLogo() gets logo from resources when INSTANCE_LOGO_SET is false`() {
|
||||
getConfiguredImageTest(ConfigurationType.INSTANCE_LOGO_SET, false) { resource ->
|
||||
val found = service.getConfiguredLogo()
|
||||
|
||||
assertEquals(resource, found)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getConfiguredLogo() gets logo from files when INSTANCE_LOGO_SET is true`() {
|
||||
getConfiguredImageTest(ConfigurationType.INSTANCE_LOGO_SET, true) { resource ->
|
||||
val found = service.getConfiguredLogo()
|
||||
|
||||
assertEquals(resource, found)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `set(configuration) set configuration in ConfigurationSource`() {
|
||||
val configuration = configuration(type = ConfigurationType.INSTANCE_NAME)
|
||||
|
@ -261,4 +324,65 @@ class ConfigurationServiceTest {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun setConfiguredImageTest(test: (MultipartFile) -> Unit) {
|
||||
val file = mockk<MultipartFile>()
|
||||
|
||||
every { service.set(any<Configuration>()) } just runs
|
||||
every { fileService.write(any<MultipartFile>(), any(), any()) } just runs
|
||||
|
||||
test(file)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setConfiguredIcon() sets icon in files`() {
|
||||
setConfiguredImageTest { file ->
|
||||
service.setConfiguredIcon(file)
|
||||
|
||||
verify {
|
||||
fileService.write(file, any(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setConfiguredIcon() sets INSTANCE_ICON_SET configuration to true`() {
|
||||
val type = ConfigurationType.INSTANCE_ICON_SET
|
||||
|
||||
setConfiguredImageTest { file ->
|
||||
service.setConfiguredIcon(file)
|
||||
|
||||
verify {
|
||||
service.set(match<Configuration> {
|
||||
it.key == type.key && it.content == true.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setConfiguredLogo() sets logo in files`() {
|
||||
setConfiguredImageTest { file ->
|
||||
service.setConfiguredLogo(file)
|
||||
|
||||
verify {
|
||||
fileService.write(file, any(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setConfiguredLogo() sets INSTANCE_LOGO_SET configuration to true`() {
|
||||
val type = ConfigurationType.INSTANCE_LOGO_SET
|
||||
|
||||
setConfiguredImageTest { file ->
|
||||
service.setConfiguredLogo(file)
|
||||
|
||||
verify {
|
||||
service.set(match<Configuration> {
|
||||
it.key == type.key && it.content == true.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import com.nhaarman.mockitokotlin2.*
|
|||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.FileService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
@ -21,7 +21,7 @@ class MaterialServiceTest :
|
|||
private val recipeService: RecipeService = mock()
|
||||
private val mixService: MixService = mock()
|
||||
private val materialTypeService: MaterialTypeService = mock()
|
||||
private val fileService: FileService = mock()
|
||||
private val fileService: WriteableFileService = mock()
|
||||
override val service: MaterialService =
|
||||
spy(MaterialServiceImpl(repository, recipeService, mixService, materialTypeService, fileService, mock()))
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import dev.fyloz.colorrecipesexplorer.model.*
|
|||
import dev.fyloz.colorrecipesexplorer.model.account.group
|
||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import io.mockk.*
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
@ -263,7 +264,7 @@ class RecipeServiceTest :
|
|||
}
|
||||
|
||||
private class RecipeImageServiceTestContext {
|
||||
val fileService = mockk<FileService> {
|
||||
val fileService = mockk<WriteableFileService> {
|
||||
every { write(any<MultipartFile>(), any(), any()) } just Runs
|
||||
every { delete(any()) } just Runs
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||
import dev.fyloz.colorrecipesexplorer.model.ConfigurationType
|
||||
import dev.fyloz.colorrecipesexplorer.model.configuration
|
||||
import dev.fyloz.colorrecipesexplorer.repository.TouchUpKitRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.*
|
||||
import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.WriteableFileService
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PdfDocument
|
||||
import dev.fyloz.colorrecipesexplorer.utils.toByteArrayResource
|
||||
import io.mockk.*
|
||||
|
@ -16,10 +15,9 @@ import kotlin.test.assertEquals
|
|||
|
||||
private class TouchUpKitServiceTestContext {
|
||||
val touchUpKitRepository = mockk<TouchUpKitRepository>()
|
||||
val fileService = mockk<FileService> {
|
||||
val fileService = mockk<WriteableFileService> {
|
||||
every { write(any<ByteArrayResource>(), any(), any()) } just Runs
|
||||
}
|
||||
val creProperties = mockk<CreProperties>()
|
||||
val configService = mockk<ConfigurationService>(relaxed = true)
|
||||
val touchUpKitService = spyk(TouchUpKitServiceImpl(fileService, configService, touchUpKitRepository))
|
||||
val pdfDocumentData = mockk<ByteArrayResource>()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package dev.fyloz.colorrecipesexplorer.service
|
||||
package dev.fyloz.colorrecipesexplorer.service.files
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||
import io.mockk.*
|
|
@ -0,0 +1,114 @@
|
|||
package dev.fyloz.colorrecipesexplorer.service.files
|
||||
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.core.io.ResourceLoader
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ResourceFileServiceTest {
|
||||
private val resourceLoader = mockk<ResourceLoader>()
|
||||
|
||||
private val service = spyk(ResourceFileService(resourceLoader))
|
||||
|
||||
@AfterEach
|
||||
fun afterEach() {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
private fun existsTest(shouldExists: Boolean, test: (String) -> Unit) {
|
||||
val path = "unit_test_resource"
|
||||
with(service) {
|
||||
every { path.fullPath() } returns mockk {
|
||||
every { resource } returns mockk {
|
||||
every { exists() } returns shouldExists
|
||||
}
|
||||
}
|
||||
|
||||
test(path)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exists() returns true when a resource exists at the given path`() {
|
||||
existsTest(true) { path ->
|
||||
val found = service.exists(path)
|
||||
|
||||
assertTrue { found }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exists() returns false when no resource exists at the given path`() {
|
||||
existsTest(false) { path ->
|
||||
val found = service.exists(path)
|
||||
|
||||
assertFalse { found }
|
||||
}
|
||||
}
|
||||
|
||||
private fun readTest(shouldExists: Boolean, test: (Resource, String) -> Unit) {
|
||||
val mockResource = mockk<Resource> {
|
||||
every { exists() } returns shouldExists
|
||||
}
|
||||
val path = "unit_test_path"
|
||||
with(service) {
|
||||
every { path.fullPath() } returns mockk {
|
||||
every { resource } returns mockResource
|
||||
}
|
||||
|
||||
test(mockResource, path)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read() returns the resource at the given path`() {
|
||||
readTest(true) { resource, path ->
|
||||
val found = service.read(path)
|
||||
|
||||
assertEquals(resource, found)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read() throws FileNotFoundException when no resource exists at the given path`() {
|
||||
readTest(false) { _, path ->
|
||||
assertThrows<FileNotFoundException> {
|
||||
service.read(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fullPath() returns the given path in the classpath`() {
|
||||
val path = "unit_test_path"
|
||||
val expectedPath = "classpath:$path"
|
||||
|
||||
with(service) {
|
||||
val found = path.fullPath()
|
||||
|
||||
assertEquals(expectedPath, found.path)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resource returns a resource for the given path`() {
|
||||
val filePath = FilePath("classpath:unit_test_path")
|
||||
val resource = mockk<Resource>()
|
||||
|
||||
every { resourceLoader.getResource(filePath.path) } returns resource
|
||||
|
||||
with(service) {
|
||||
val found = filePath.resource
|
||||
|
||||
assertEquals(resource, found)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue