implement migration
This commit is contained in:
@@ -30,8 +30,6 @@ dependencies {
|
||||
implementation(libs.exposed.dao)
|
||||
implementation(libs.exposed.jdbc)
|
||||
implementation(libs.postgres)
|
||||
implementation(libs.flyway)
|
||||
implementation(libs.flyway.postgres)
|
||||
implementation(libs.koin)
|
||||
implementation(libs.koin.logger)
|
||||
implementation(libs.random)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
[versions]
|
||||
kotlin = "2.1.21"
|
||||
kotlin = "2.2.10"
|
||||
ktor = "3.1.3"
|
||||
exposed = "0.60.0"
|
||||
postgres = "42.7.5"
|
||||
flyway = "11.7.0"
|
||||
koin = "4.0.3"
|
||||
random = "1.0.0"
|
||||
otp = "1.1.1"
|
||||
@@ -32,8 +31,6 @@ exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "expose
|
||||
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
|
||||
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||
postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" }
|
||||
flyway = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }
|
||||
flyway-postgres = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway" }
|
||||
|
||||
koin = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }
|
||||
koin-logger = { module = "io.insert-koin:koin-logger-slf4j", version.ref = "koin" }
|
||||
|
||||
BIN
backend/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
backend/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
10
backend/gradlew
vendored
10
backend/gradlew
vendored
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -112,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -203,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
@@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
6
backend/gradlew.bat
vendored
6
backend/gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -68,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.infendro.account
|
||||
|
||||
import com.infendro.account.config.*
|
||||
import com.infendro.account.model.initializeDatabase
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.cio.*
|
||||
|
||||
@@ -10,13 +9,14 @@ fun main(
|
||||
) = EngineMain.main(args)
|
||||
|
||||
fun Application.module() {
|
||||
configureDatabase()
|
||||
configureDependencyInjection()
|
||||
|
||||
configureDatabase()
|
||||
|
||||
configureSecurity()
|
||||
configureRouting()
|
||||
|
||||
configureSerialization()
|
||||
configureValidation()
|
||||
configureRouting()
|
||||
configureException()
|
||||
|
||||
initializeDatabase()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.infendro.account.config
|
||||
|
||||
import com.infendro.account.model.migrate
|
||||
import io.ktor.server.application.Application
|
||||
import org.flywaydb.core.Flyway
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
|
||||
fun Application.configureDatabase() {
|
||||
@@ -12,9 +12,5 @@ fun Application.configureDatabase() {
|
||||
user = config.username,
|
||||
password = config.password,
|
||||
)
|
||||
|
||||
val flyway = Flyway.configure()
|
||||
.dataSource(config.url, config.username, config.password)
|
||||
.load()
|
||||
flyway.migrate()
|
||||
migrate()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.infendro.account.config
|
||||
|
||||
import com.infendro.account.model.repository.AccessRepository
|
||||
import com.infendro.account.model.repository.AccountRepository
|
||||
import com.infendro.account.model.repository.InitializationRepository
|
||||
import com.infendro.account.model.repository.MigrationRepository
|
||||
import com.infendro.account.model.repository.SessionRepository
|
||||
import com.infendro.account.service.AccessService
|
||||
import com.infendro.account.service.AccountService
|
||||
@@ -22,7 +22,7 @@ fun Application.configureDependencyInjection() {
|
||||
}
|
||||
|
||||
private val module = module {
|
||||
singleOf(::InitializationRepository)
|
||||
singleOf(::MigrationRepository)
|
||||
singleOf(::AccessRepository)
|
||||
singleOf(::AccountRepository)
|
||||
singleOf(::SessionRepository)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.infendro.account.model
|
||||
|
||||
import com.infendro.account.model.entity.InitializationTable
|
||||
import com.infendro.account.model.repository.AccountRepository
|
||||
import com.infendro.account.model.repository.InitializationRepository
|
||||
import com.infendro.account.util.SecureHasher
|
||||
import io.ktor.server.application.Application
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.koin.ktor.ext.inject
|
||||
|
||||
fun Application.initializeDatabase() {
|
||||
owner()
|
||||
}
|
||||
|
||||
private fun Application.owner() = initialize("owner") {
|
||||
val accountRepository by inject<AccountRepository>()
|
||||
|
||||
accountRepository.insert {
|
||||
val salt = SecureHasher.generateSalt()
|
||||
|
||||
this.username = "infendro"
|
||||
this.passwordHash = SecureHasher.hash("password", salt)
|
||||
this.passwordSalt = salt
|
||||
this.secret = "deadbeef"
|
||||
this.role = Role.OWNER
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.initialize(
|
||||
name: String,
|
||||
block: () -> Unit,
|
||||
) {
|
||||
val initializationRepository by inject<InitializationRepository>()
|
||||
|
||||
initializationRepository
|
||||
.singleOrNull { InitializationTable.name eq name }
|
||||
?.run { return }
|
||||
|
||||
block()
|
||||
|
||||
initializationRepository.insert {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.infendro.account.model
|
||||
|
||||
import com.infendro.account.model.entity.MigrationTable
|
||||
import com.infendro.account.model.repository.AccountRepository
|
||||
import com.infendro.account.model.repository.MigrationRepository
|
||||
import com.infendro.account.util.SecureHasher
|
||||
import io.ktor.server.application.*
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.Transaction
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.koin.ktor.ext.inject
|
||||
|
||||
fun Application.migrate() {
|
||||
transaction {
|
||||
SchemaUtils.create(MigrationTable)
|
||||
}
|
||||
|
||||
migration(
|
||||
"initial",
|
||||
"""
|
||||
CREATE TABLE access
|
||||
(
|
||||
id BIGSERIAL
|
||||
PRIMARY KEY,
|
||||
token_hash TEXT
|
||||
NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE account
|
||||
(
|
||||
id BIGSERIAL
|
||||
PRIMARY KEY,
|
||||
username TEXT
|
||||
UNIQUE
|
||||
NOT NULL,
|
||||
password_hash TEXT
|
||||
NOT NULL,
|
||||
password_salt TEXT
|
||||
NOT NULL,
|
||||
secret TEXT
|
||||
NOT NULL,
|
||||
role TEXT
|
||||
NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE session
|
||||
(
|
||||
id BIGSERIAL
|
||||
PRIMARY KEY,
|
||||
token_hash TEXT
|
||||
NOT NULL,
|
||||
account_id BIGINT
|
||||
REFERENCES account (id) ON DELETE CASCADE
|
||||
NOT NULL
|
||||
);
|
||||
""".trimIndent()
|
||||
)
|
||||
migration("owner") {
|
||||
val accounts by inject<AccountRepository>()
|
||||
|
||||
accounts.insert {
|
||||
val salt = SecureHasher.generateSalt()
|
||||
|
||||
this.username = "infendro"
|
||||
this.passwordHash = SecureHasher.hash("password", salt)
|
||||
this.passwordSalt = salt
|
||||
this.secret = "deadbeef"
|
||||
this.role = Role.OWNER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.migration(
|
||||
name: String,
|
||||
block: Transaction.() -> Unit,
|
||||
) {
|
||||
val migrations by inject<MigrationRepository>()
|
||||
|
||||
migrations
|
||||
.singleOrNull { MigrationTable.name eq name }
|
||||
?.run { return }
|
||||
|
||||
transaction {
|
||||
block()
|
||||
}
|
||||
|
||||
migrations.insert {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.migration(
|
||||
name: String,
|
||||
@Language("sql") sql: String,
|
||||
) = migration(name) {
|
||||
exec(sql)
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import org.jetbrains.exposed.dao.LongEntityClass
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||
|
||||
object InitializationTable : LongIdTable("initialization", "id") {
|
||||
object MigrationTable : LongIdTable("migration", "id") {
|
||||
val name = text("name")
|
||||
}
|
||||
|
||||
class InitializationEntity(id: EntityID<Long>) : LongEntity(id) {
|
||||
companion object : LongEntityClass<InitializationEntity>(InitializationTable)
|
||||
class MigrationEntity(id: EntityID<Long>) : LongEntity(id) {
|
||||
companion object : LongEntityClass<MigrationEntity>(MigrationTable)
|
||||
|
||||
var name by InitializationTable.name
|
||||
var name by MigrationTable.name
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.infendro.account.model.repository
|
||||
|
||||
import com.infendro.account.model.entity.InitializationEntity
|
||||
|
||||
class InitializationRepository : Repository<InitializationEntity>(InitializationEntity)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.infendro.account.model.repository
|
||||
|
||||
import com.infendro.account.model.entity.MigrationEntity
|
||||
|
||||
class MigrationRepository : Repository<MigrationEntity>(MigrationEntity)
|
||||
@@ -1,44 +0,0 @@
|
||||
CREATE TABLE initialization
|
||||
(
|
||||
id BIGSERIAL
|
||||
PRIMARY KEY,
|
||||
name TEXT
|
||||
UNIQUE
|
||||
NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE access
|
||||
(
|
||||
id BIGSERIAL
|
||||
PRIMARY KEY,
|
||||
token_hash TEXT
|
||||
NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE account
|
||||
(
|
||||
id BIGSERIAL
|
||||
PRIMARY KEY,
|
||||
username TEXT
|
||||
UNIQUE
|
||||
NOT NULL,
|
||||
password_hash TEXT
|
||||
NOT NULL,
|
||||
password_salt TEXT
|
||||
NOT NULL,
|
||||
secret TEXT
|
||||
NOT NULL,
|
||||
role TEXT
|
||||
NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE session
|
||||
(
|
||||
id BIGSERIAL
|
||||
PRIMARY KEY,
|
||||
token_hash TEXT
|
||||
NOT NULL,
|
||||
account_id BIGINT
|
||||
REFERENCES account (id) ON DELETE CASCADE
|
||||
NOT NULL
|
||||
);
|
||||
Reference in New Issue
Block a user