Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions config/detekt/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ complexity:
LongMethod:
# Seed and fixture builders are long because the data is long — straight
# line, no branches. Production code keeps detekt's 60-line ceiling.
excludes: ['**/test/**', '**/integrationTest/**', '**/testFixtures/**', '**/db/migration/**']
excludes: ['**/test/**', '**/integrationTest/**', '**/testFixtures/**', '**/db/migration/**', '**/ShippedBoards.kt', '**/ShippedEsports.kt']
TooManyFunctions:
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/integrationTest/**', '**/testFixtures/**', '**/db/migration/**']
# A Spring service that owns one aggregate lands at twelve to fourteen
Expand Down Expand Up @@ -110,7 +110,9 @@ style:
# sits in: `@Order(50)` says what 50 is, and hoisting it to a constant puts
# the ordering one indirection away from the thing being ordered.
ignoreAnnotation: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/integrationTest/**', '**/testFixtures/**', '**/db/migration/**', '**/*.kts']
# The two seed loaders set JDBC parameters by position: the number is the
# position, and a constant naming it would name nothing.
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/integrationTest/**', '**/testFixtures/**', '**/db/migration/**', '**/ShippedBoards.kt', '**/ShippedEsports.kt', '**/*.kts']
MaxLineLength:
excludes: ['**/db/migration/**']
# ktlint is the other gate landing on this job and its ktlint_official style
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package net.blueshell.api.board.domain

import db.migration.R__Boards_seed
import net.blueshell.api.board.persistence.BoardMemberRepository
import net.blueshell.api.board.persistence.BoardRepository
import net.blueshell.api.file.api.FileService
Expand All @@ -10,7 +9,6 @@ import net.blueshell.api.shared.enums.FileType
import net.blueshell.api.shared.enums.Role
import net.blueshell.api.testsupport.UserTestSupport
import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.api.migration.Context
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -19,7 +17,6 @@ import org.springframework.boot.test.context.SpringBootTest
import java.io.ByteArrayInputStream
import java.nio.file.Files
import java.nio.file.Paths
import java.sql.Connection
import javax.sql.DataSource

/**
Expand Down Expand Up @@ -49,15 +46,7 @@ class ShippedBoardArtIT : UserTestSupport() {

@BeforeEach
fun loadTheRecords() {
dataSource.connection.use { connection ->
R__Boards_seed().migrate(
object : Context {
override fun getConfiguration() = null

override fun getConnection(): Connection = connection
},
)
}
ShippedBoards(dataSource, transactionTemplate).apply()
}

/** One board's photograph, read where its widths can still be read. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package net.blueshell.api.board.persistence

import db.migration.R__Boards_seed
import net.blueshell.api.board.domain.BoardSeed
import net.blueshell.api.board.domain.ShippedBoards
import net.blueshell.api.shared.enums.Role
import net.blueshell.api.testsupport.UserTestSupport
import net.blueshell.api.user.persistence.User
import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.api.migration.Context
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import java.sql.Connection
import java.time.LocalDate
import javax.sql.DataSource

Expand Down Expand Up @@ -420,15 +418,7 @@ class BoardSeedLoadIT : UserTestSupport() {
)!!

private fun runLoader() {
dataSource.connection.use { connection ->
R__Boards_seed().migrate(
object : Context {
override fun getConfiguration() = null

override fun getConnection(): Connection = connection
},
)
}
ShippedBoards(dataSource, transactionTemplate).apply()
}

private companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package net.blueshell.api.esports.domain

import db.migration.R__Esports_seed
import net.blueshell.api.esports.persistence.GameRepository
import net.blueshell.api.esports.persistence.TeamRepository
import net.blueshell.api.esports.persistence.TeamSeason
Expand All @@ -14,7 +13,6 @@ import net.blueshell.api.testsupport.EsportsSeedFixture
import net.blueshell.api.testsupport.UserTestSupport
import net.blueshell.api.user.api.UserService
import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.api.migration.Context
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
Expand All @@ -24,7 +22,6 @@ import org.springframework.boot.test.context.SpringBootTest
import java.io.ByteArrayInputStream
import java.nio.file.Files
import java.nio.file.Paths
import java.sql.Connection
import javax.sql.DataSource

/**
Expand Down Expand Up @@ -65,15 +62,7 @@ class ShippedArtIT : UserTestSupport() {

@BeforeEach
fun loadTheRecords() {
dataSource.connection.use { connection ->
R__Esports_seed(EsportsSeedFixture.files).migrate(
object : Context {
override fun getConfiguration() = null

override fun getConnection(): Connection = connection
},
)
}
ShippedEsports(dataSource, transactionTemplate, EsportsSeedFixture.files).apply()
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package net.blueshell.api.esports.persistence

import db.migration.R__Esports_seed
import net.blueshell.api.testsupport.EsportsSeedFixture
import net.blueshell.api.esports.domain.ShippedEsports
import net.blueshell.api.testsupport.UserTestSupport
import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.api.migration.Context
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import java.sql.Connection
import javax.sql.DataSource

/**
Expand Down Expand Up @@ -221,15 +219,7 @@ class EsportsSeedLoadIT : UserTestSupport() {
}

private fun runLoader() {
dataSource.connection.use { connection ->
R__Esports_seed(EsportsSeedFixture.files).migrate(
object : Context {
override fun getConfiguration() = null

override fun getConnection(): Connection = connection
},
)
}
ShippedEsports(dataSource, transactionTemplate, EsportsSeedFixture.files).apply()
}

@AfterEach
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package net.blueshell.api.esports.persistence

import db.migration.R__Esports_seed
import net.blueshell.api.shared.enums.Role
import net.blueshell.api.esports.domain.ShippedEsports
import net.blueshell.api.testsupport.EsportsSeedFixture
import net.blueshell.api.testsupport.UserTestSupport
import net.blueshell.api.user.persistence.User
import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.api.migration.Context
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.jdbc.core.JdbcTemplate
import java.sql.Connection
import javax.sql.DataSource

/**
Expand Down Expand Up @@ -120,15 +118,7 @@ class RecoveredAttributionIT : UserTestSupport() {
}

private fun runLoader() {
dataSource.connection.use { connection ->
R__Esports_seed(EsportsSeedFixture.files).migrate(
object : Context {
override fun getConfiguration() = null

override fun getConnection(): Connection = connection
},
)
}
ShippedEsports(dataSource, transactionTemplate, EsportsSeedFixture.files).apply()
}

@AfterEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import net.blueshell.api.file.persistence.File
import net.blueshell.api.shared.enums.FileType
import net.blueshell.api.user.api.UserService
import net.blueshell.api.user.persistence.User
import net.blueshell.api.shared.seed.SeedOrder
import org.slf4j.LoggerFactory
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionTemplate

Expand Down Expand Up @@ -238,6 +240,7 @@ class ShippedBoardArt(
class ShippedBoardArtOnStartup(
private val art: ShippedBoardArt,
) {
@Order(SeedOrder.ART)
@EventListener(ApplicationReadyEvent::class)
fun onReady() {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
package db.migration
package net.blueshell.api.board.domain

import net.blueshell.api.board.domain.BoardSeed
import net.blueshell.api.shared.seed.SeedCsv
import org.flywaydb.core.api.migration.BaseJavaMigration
import org.flywaydb.core.api.migration.Context
import net.blueshell.api.shared.seed.SeedOrder
import org.slf4j.LoggerFactory
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
import org.springframework.core.annotation.Order
import org.springframework.jdbc.datasource.DataSourceUtils
import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionTemplate
import java.sql.Connection
import java.sql.Date
import java.sql.Types
import javax.sql.DataSource

/**
* Loads the boards and their members from the seed files under `db/seed/boards`.
*
* Repeatable and keyed on the files' contents, so correcting a row is an edit and a deploy.
* Deletion outranks the files: a soft-deleted board or member stays deleted while its row is
* still listed, and removing the row is how it leaves for good. The recorded name is the key,
* so the files can correct a role, a nickname, a blurb or a photograph but not a name.
* A member is attached to the account matching their name once and never re-matched, so
* detaching somebody stays detached. `photo` and `portrait` are not written here: storing a
* picture needs the volume and converter a migration runner lacks, so `ShippedBoardArt` fills
* them once the application is up.
* Loads the boards from `db/seed/boards`, upserting on the recorded name so a second run is a no-op.
* Deletion outranks the files, an attached account is never re-matched, and the art follows separately.
*/
@Suppress("unused", "ClassNaming")
class R__Boards_seed : BaseJavaMigration() {
/** Hashes the files' contents: Flyway re-runs a repeatable migration when its checksum moves. */
override fun getChecksum(): Int = SEED_FILES.fold(11) { acc, name -> 31 * acc + read(name).hashCode() }
@Component
class ShippedBoards(
private val dataSource: DataSource,
private val transactions: TransactionTemplate,
private val seed: SeedCsv = BoardSeed.files,
) {
data class Applied(
val boards: Int,
val members: Int,
val leftDeleted: Int,
)

override fun migrate(context: Context) {
val connection = context.connection
// One transaction, as the migration had. DataSourceUtils returns the connection it is bound to.
fun apply(): Applied =
transactions.execute {
val connection = DataSourceUtils.getConnection(dataSource)
try {
load(connection)
} finally {
DataSourceUtils.releaseConnection(connection, dataSource)
}
}!!

private fun load(connection: Connection): Applied {
val boards = parse(read("boards.csv"))
val members = parse(read("members.csv"))

Expand Down Expand Up @@ -56,6 +70,11 @@ class R__Boards_seed : BaseJavaMigration() {
written,
outcomes.size - written,
)
return Applied(
boards = boardIds.values.count { it != null },
members = written,
leftDeleted = outcomes.size - written,
)
}

/** What became of one member the file lists. */
Expand Down Expand Up @@ -260,11 +279,10 @@ class R__Boards_seed : BaseJavaMigration() {
statement.executeQuery().use { rows -> if (rows.next()) rows.getLong(1) else null }
}

private fun read(name: String): String = BoardSeed.files.read(name)
private fun read(name: String): String = seed.read(name)

companion object {
private val log = LoggerFactory.getLogger(R__Boards_seed::class.java)
private val SEED_FILES = listOf("boards.csv", "members.csv")
private val log = LoggerFactory.getLogger(ShippedBoards::class.java)

/** The sentinel a live row carries, as every soft-deleted table here uses it. */
private const val ACTIVE = "deleted_at = '9999-12-31 23:59:59'"
Expand All @@ -278,3 +296,25 @@ class R__Boards_seed : BaseJavaMigration() {
fun parse(content: String): List<Map<String, String>> = SeedCsv.parse(content)
}
}

/**
* A separate bean so the transaction is opened by the proxy, and a failure never blocks start-up.
*/
@Component
class ShippedBoardsOnStartup(
private val boards: ShippedBoards,
) {
@Order(SeedOrder.RECORDS)
@EventListener(ApplicationReadyEvent::class)
fun onReady() {
try {
boards.apply()
} catch (e: Exception) {
log.warn("[boards-seed] could not load the boards that ship: {}", e.message)
}
}

private companion object {
val log = LoggerFactory.getLogger(ShippedBoardsOnStartup::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import net.blueshell.api.file.api.FileService
import net.blueshell.api.file.persistence.File
import net.blueshell.api.shared.enums.FileType
import net.blueshell.api.shared.seed.SeedCsv
import net.blueshell.api.shared.seed.SeedOrder
import net.blueshell.api.user.api.UserService
import net.blueshell.api.user.persistence.User
import org.slf4j.LoggerFactory
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionTemplate

Expand Down Expand Up @@ -240,6 +242,7 @@ class ShippedArt(
class ShippedArtOnStartup(
private val art: ShippedArt,
) {
@Order(SeedOrder.ART)
@EventListener(ApplicationReadyEvent::class)
fun onReady() {
try {
Expand Down
Loading
Loading