3. R2DBC Repositories
Create a package com.endlessuphill.regent.data.repository. Inside, create repository interfaces:
// UserRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.User
import kotlinx.coroutines.flow.Flow
import org.springframework.data.r2dbc.repository.Query
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface UserRepository : CoroutineCrudRepository<User, UUID> {
// Use suspend fun for single results or optional results
suspend fun findByUsername(username: String): User?
// Use Flow for multiple results
fun findAllByIdIn(ids: Collection<UUID>): Flow<User>
}// ProjectRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.Project
import kotlinx.coroutines.flow.Flow
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface ProjectRepository : CoroutineCrudRepository<Project, UUID> {
suspend fun findByName(name: String): Project?
// We'll need a way to find projects a user belongs to - requires joins later
}// ProjectUserRoleRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.ProjectUserRole
import com.endlessuphill.regent.data.model.enums.Role
import kotlinx.coroutines.flow.Flow
import org.springframework.data.r2dbc.repository.Query
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface ProjectUserRoleRepository : CoroutineCrudRepository<ProjectUserRole, UUID> {
fun findByProjectId(projectId: UUID): Flow<ProjectUserRole>
fun findByUserId(userId: UUID): Flow<ProjectUserRole>
suspend fun findByProjectIdAndUserId(projectId: UUID, userId: UUID): ProjectUserRole?
suspend fun deleteByProjectIdAndUserId(projectId: UUID, userId: UUID): Long // Returns number deleted
// Query to get projects for a user
@Query("SELECT p.* FROM projects p JOIN project_user_roles pur ON p.id = pur.project_id WHERE pur.user_id = :userId")
fun findProjectsByUserId(userId: UUID): Flow<Project> // Need Project entity here
}// WorkerDefinitionRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.WorkerDefinition
import kotlinx.coroutines.flow.Flow
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface WorkerDefinitionRepository : CoroutineCrudRepository<WorkerDefinition, UUID> {
fun findByProjectId(projectId: UUID): Flow<WorkerDefinition>
suspend fun findByProjectIdAndName(projectId: UUID, name: String): WorkerDefinition?
}// JobRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.Job
import com.endlessuphill.regent.data.model.enums.JobStatus
import kotlinx.coroutines.flow.Flow
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface JobRepository : CoroutineCrudRepository<Job, UUID> {
// Example for pagination/filtering later
fun findByProjectIdAndStatus(projectId: UUID, status: JobStatus, pageable: Pageable): Flow<Job>
fun findByProjectId(projectId: UUID, pageable: Pageable): Flow<Job>
fun findByProjectId(projectId: UUID): Flow<Job> // Non-paginated version
}// JobLogRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.JobLog
import kotlinx.coroutines.flow.Flow
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface JobLogRepository : CoroutineCrudRepository<JobLog, UUID> {
fun findByJobIdOrderByTimestampAsc(jobId: UUID): Flow<JobLog>
}// ScheduleRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.Schedule
import kotlinx.coroutines.flow.Flow
import org.springframework.data.r2dbc.repository.Query
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.time.Instant
import java.util.UUID
@Repository
interface ScheduleRepository : CoroutineCrudRepository<Schedule, UUID> {
fun findByProjectId(projectId: UUID): Flow<Schedule>
@Query("SELECT * FROM schedules WHERE is_enabled = true AND next_run_at <= :currentTime")
fun findDueSchedules(currentTime: Instant): Flow<Schedule>
}// RegentLogRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.RegentLog
import kotlinx.coroutines.flow.Flow
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface RegentLogRepository : CoroutineCrudRepository<RegentLog, UUID> {
// Example pagination
fun findAllByOrderByTimestampDesc(pageable: Pageable): Flow<RegentLog>
}
// --- Coroutine Extension ---
// Add the kotlinx-coroutines-reactor dependency if not already added by Spring Initializr
// build.gradle.kts: implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
// We switched to CoroutineCrudRepository which simplifies this, but Reactive*Repository alternatives exist.// WorkerDefinitionRepository.kt
package com.endlessuphill.regent.data.repository
import com.endlessuphill.regent.data.model.WorkerDefinition
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
import org.springframework.stereotype.Repository
import java.util.UUID
import kotlinx.coroutines.flow.Flow
@Repository
interface WorkerDefinitionRepository : CoroutineCrudRepository<WorkerDefinition, UUID> {
suspend fun findByName(name: String): WorkerDefinition?
suspend fun findByProjectId(projectId: UUID): Flow<WorkerDefinition>
suspend fun findByProjectIdAndName(projectId: UUID, name: String): WorkerDefinition?
}We have used CoroutineCrudRepository instead of ReactiveCrudRepository for all repositories. This allows us to use suspend functions for single results and Flow for multiple results, which is more idiomatic in Kotlin.
Note: The @Query annotation is used for custom queries. You can also use Spring Dataβs derived query methods for simple cases.
Why always me?