Merge branch 'database-manager-integration' into 'master'

Resolve "Ajouter un système pour détecter et mettre à jour la base de données"

Closes #32

See merge request color-recipes-explorer/backend!6
This commit is contained in:
William Nolin 2021-03-06 22:36:58 +00:00
commit ce8ed98fd4
15 changed files with 169 additions and 22 deletions

2
.gitignore vendored
View File

@ -8,7 +8,7 @@
gradle/
build/
logs/
workdir/
data/
dokka/
dist/

View File

@ -30,6 +30,7 @@ dependencies {
implementation("org.apache.pdfbox:pdfbox:2.0.4")
implementation("com.atlassian.commonmark:commonmark:0.13.1")
implementation("commons-io:commons-io:2.6")
implementation("dev.fyloz.colorrecipesexplorer:database-manager:1.0.1")
implementation("org.springframework.boot:spring-boot-starter-data-jpa:2.3.4.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-jdbc:2.3.4.RELEASE")
@ -54,7 +55,6 @@ dependencies {
runtimeOnly("org.postgresql:postgresql:42.2.16")
runtimeOnly("com.microsoft.sqlserver:mssql-jdbc:9.2.1.jre11")
compileOnly("org.projectlombok:lombok:1.18.10")
}

View File

@ -2,19 +2,20 @@ package dev.fyloz.trial.colorrecipesexplorer
import dev.fyloz.trial.colorrecipesexplorer.config.properties.CREProperties
import dev.fyloz.trial.colorrecipesexplorer.config.properties.MaterialTypeProperties
import dev.fyloz.trial.colorrecipesexplorer.repository.MaterialTypeRepository
import dev.fyloz.trial.colorrecipesexplorer.repository.NamedJpaRepository
import org.slf4j.Logger
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.FilterType
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.context.annotation.Bean
import javax.sql.DataSource
@SpringBootApplication
@EnableConfigurationProperties(MaterialTypeProperties::class, CREProperties::class)
@SpringBootApplication(exclude = [LiquibaseAutoConfiguration::class])
@EnableConfigurationProperties(MaterialTypeProperties::class, CREProperties::class, DatabaseUpdaterProperties::class)
class ColorRecipesExplorerApplication
fun main(args: Array<String>) {
runApplication<ColorRecipesExplorerApplication>(*args)
runApplication<ColorRecipesExplorerApplication>()
}

View File

@ -0,0 +1,126 @@
package dev.fyloz.trial.colorrecipesexplorer
import dev.fyloz.colorrecipesexplorer.databasemanager.CreDatabase
import dev.fyloz.colorrecipesexplorer.databasemanager.databaseContext
import dev.fyloz.colorrecipesexplorer.databasemanager.databaseUpdaterProperties
import org.slf4j.Logger
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
import javax.sql.DataSource
const val SUPPORTED_DATABASE_VERSION = 2
const val ENV_VAR_ENABLE_DATABASE_UPDATE_NAME = "CRE_ENABLE_DB_UPDATE"
val DATABASE_NAME_REGEX = Regex("(\\w+)$")
@Configuration
class DataSourceConfiguration {
@Bean(name = ["dataSource"])
@ConfigurationProperties(prefix = "spring.datasource")
fun customDataSource(
logger: Logger,
environment: Environment,
databaseUpdaterProperties: DatabaseUpdaterProperties
): DataSource {
val databaseUrl: String = environment.getProperty("spring.datasource.url")!!
runDatabaseVersionCheck(logger, databaseUrl, databaseUpdaterProperties)
return DataSourceBuilder
.create()
.url(databaseUrl) // Hikari won't start without that
.build()
}
}
/**
* Runs a database version check. If the database's version is not supported and the environment variable 'CRE_ENABLE_DB_UPDATE' is:
* - 1: The database will be updated.
* - Any other value: The application will not start.
*/
fun runDatabaseVersionCheck(logger: Logger, databaseUrl: String, databaseUpdaterProperties: DatabaseUpdaterProperties) {
logger.info("Verifying database's version before starting...")
if (":h2:" in databaseUrl) {
logger.warn("H2 is not supported by the database manager")
return
}
val databaseUpdateEnabled = System.getenv(ENV_VAR_ENABLE_DATABASE_UPDATE_NAME) == "1"
val database = getDatabase(databaseUrl, databaseUpdaterProperties, logger)
val version = database.version
if (version != SUPPORTED_DATABASE_VERSION) {
if (!databaseUpdateEnabled) {
database.close()
throwUnsupportedDatabaseVersion(version, logger)
} else runDatabaseUpdate(logger, database)
} else {
logger.info("The database is up to date!")
if (databaseUpdateEnabled) {
logger.warn("The database is up to date but the environment variable '$ENV_VAR_ENABLE_DATABASE_UPDATE_NAME' is set to '1'. Set it to '0' to prevent any possible data loss.")
}
}
database.close()
}
/** Updates the given [database]. */
fun runDatabaseUpdate(logger: Logger, database: CreDatabase) {
logger.info("The environment variable '$ENV_VAR_ENABLE_DATABASE_UPDATE_NAME' is set to '1'; The database will be updated.")
database.update()
}
fun getDatabase(
databaseUrl: String,
databaseUpdaterProperties: DatabaseUpdaterProperties,
logger: Logger
): CreDatabase {
val databaseName =
(DATABASE_NAME_REGEX.find(databaseUrl) ?: throw DatabaseVersioningException.InvalidUrl(databaseUrl)).value
return CreDatabase(
databaseContext(
properties = databaseUpdaterProperties(
targetVersion = SUPPORTED_DATABASE_VERSION,
url = databaseUrl.removeSuffix(databaseName),
dbName = databaseName,
username = databaseUpdaterProperties.username,
password = databaseUpdaterProperties.password
),
logger
)
)
}
fun throwUnsupportedDatabaseVersion(version: Int, logger: Logger) {
if (version > SUPPORTED_DATABASE_VERSION) {
logger.error("Version $version of the database is not supported; Only version $SUPPORTED_DATABASE_VERSION is currently supported; Update this application to use the database.")
} else {
logger.error(
"""Version $version of the database is not supported; Only version $SUPPORTED_DATABASE_VERSION is currently supported.
|You can update the database to the supported version by either:
| - Setting the environment variable '$ENV_VAR_ENABLE_DATABASE_UPDATE_NAME' to '1' to update the database automatically
| - Updating the database manually with the database manager utility (https://git.fyloz.dev/color-recipes-explorer/database-manager)
|
|Don't forget to backup the database before upgrading to prevent any data loss.
""".trimMargin()
)
}
throw DatabaseVersioningException.UnsupportedDatabaseVersion(version)
}
@ConfigurationProperties(prefix = "database-updater")
class DatabaseUpdaterProperties {
var username: String = ""
var password: String = ""
}
sealed class DatabaseVersioningException(message: String) : Exception(message) {
class InvalidUrl(url: String) : DatabaseVersioningException("Invalid database url: $url")
class UnsupportedDatabaseVersion(version: Int) :
DatabaseVersioningException("Unsupported database version: $version; Only version $SUPPORTED_DATABASE_VERSION is currently supported")
}

View File

@ -1,7 +1,6 @@
spring.datasource.url=jdbc:h2:mem:cre
#spring.datasource.url=jdbc:h2:file:./workdir/recipes
spring.datasource.username=sa
spring.datasource.password=LWK4Y7TvEbNyhu1yCoG3
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.path=/dbconsole
spring.h2.console.enabled=true

View File

@ -1,4 +1,4 @@
spring.datasource.url=jdbc:mysql://172.20.0.2/cre
spring.datasource.url=jdbc:mysql://172.66.1.1/cre
spring.datasource.username=root
spring.datasource.password=pass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

View File

@ -1,7 +1,7 @@
# PORT
server.port=9090
# CRE
cre.server.upload-directory=./workdir
cre.server.upload-directory=data
cre.server.password-file=passwords.txt
cre.server.url-use-port=true
cre.server.url-use-https=false
@ -10,10 +10,8 @@ cre.security.jwt-duration=18000000
# Root user
cre.security.root.id=9999
cre.security.root.password=password
# Common user
#cre.security.common.id=9998
#cre.security.common.password=common
# TYPES DE PRODUIT PAR DÉFAUT
# Default material types
entities.material-types.systemTypes[0].name=Aucun
entities.material-types.systemTypes[0].prefix=
entities.material-types.systemTypes[0].usepercentages=false
@ -21,9 +19,14 @@ entities.material-types.systemTypes[1].name=Base
entities.material-types.systemTypes[1].prefix=BAS
entities.material-types.systemTypes[1].usepercentages=false
entities.material-types.baseName=Base
# Database manager
database-updater.username=root
database-updater.password=pass
# DEBUG
spring.jpa.show-sql=true
# NE PAS MODIFIER
# Do not modify
spring.messages.fallback-to-system-locale=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
@ -31,4 +34,5 @@ spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=true
server.http2.enabled=true
server.error.whitelabel.enabled=false
spring.h2.console.enabled=false
spring.profiles.active=@spring.profiles.active@

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable
%black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{36}): %msg%n%throwable
</Pattern>
</layout>
</appender>
@ -20,7 +21,7 @@
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>
%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n
%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n
</Pattern>
</encoder>

View File

@ -4,12 +4,14 @@ import dev.fyloz.trial.colorrecipesexplorer.model.EmployeeGroup
import dev.fyloz.trial.colorrecipesexplorer.model.employee
import dev.fyloz.trial.colorrecipesexplorer.model.employeeGroup
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import kotlin.test.*
@Disabled
@DataJpaTest
class EmployeeRepositoryTest @Autowired constructor(private val employeeRepository: EmployeeRepository, val entityManager: TestEntityManager) {
private var employeeGroup = employeeGroup()
@ -94,6 +96,7 @@ class EmployeeRepositoryTest @Autowired constructor(private val employeeReposito
}
}
@Disabled
class EmployeeGroupRepositoryTest @Autowired constructor(employeeGroupRepository: EmployeeGroupRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<EmployeeGroup, EmployeeGroupRepository>(employeeGroupRepository, entityManager) {
override fun entity(name: String): EmployeeGroup = employeeGroup(name = name)

View File

@ -2,10 +2,12 @@ package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.Company
import dev.fyloz.trial.colorrecipesexplorer.model.company
import org.junit.jupiter.api.Disabled
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
@Disabled
@DataJpaTest
class CompanyRepositoryTest @Autowired constructor(companyRepository: CompanyRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<Company, CompanyRepository>(companyRepository, entityManager) {

View File

@ -3,12 +3,14 @@ package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.Material
import dev.fyloz.trial.colorrecipesexplorer.model.material
import dev.fyloz.trial.colorrecipesexplorer.model.materialType
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@Disabled
class MaterialRepositoryTest @Autowired constructor(materialRepository: MaterialRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<Material, MaterialRepository>(materialRepository, entityManager) {
override fun entity(name: String): Material = material(name = name, materialType = null)

View File

@ -2,11 +2,13 @@ package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.MaterialType
import dev.fyloz.trial.colorrecipesexplorer.model.materialType
import org.junit.jupiter.api.Disabled
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import org.junit.jupiter.api.Test
import kotlin.test.*
@Disabled
class MaterialTypeRepositoryTest @Autowired constructor(materialTypeRepository: MaterialTypeRepository, entityManager: TestEntityManager) :
AbstractNamedJpaRepositoryTest<MaterialType, MaterialTypeRepository>(materialTypeRepository, entityManager) {
override fun entity(name: String): MaterialType = entity(name = name, prefix = "MAT")

View File

@ -1,9 +1,11 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import org.junit.jupiter.api.Disabled
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
@Disabled
@DataJpaTest
class MixMaterialRepositoryTest @Autowired constructor(
private val mixMaterialRepository: MixMaterialRepository,

View File

@ -1,9 +1,11 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import org.junit.jupiter.api.Disabled
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
@Disabled
@DataJpaTest
class RecipeStepRepositoryTest @Autowired constructor(
recipeStepRepository: RecipeStepRepository,

View File

@ -1,12 +1,15 @@
package dev.fyloz.trial.colorrecipesexplorer.repository
import dev.fyloz.trial.colorrecipesexplorer.model.NamedModel
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
import kotlin.test.*
@DataJpaTest
@DataJpaTest(excludeAutoConfiguration = [LiquibaseAutoConfiguration::class])
abstract class AbstractNamedJpaRepositoryTest<E : NamedModel, R : NamedJpaRepository<E>>(
protected val repository: R,
protected val entityManager: TestEntityManager