fixing web-app

This commit is contained in:
stefan
2025-09-24 14:21:57 +02:00
parent cd2b0796a6
commit 1c4184809a
156 changed files with 440 additions and 1708 deletions
@@ -0,0 +1,32 @@
plugins {
// kotlin("jvm")
// kotlin("plugin.spring")
// kotlin("plugin.jpa") version "2.1.21"
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.spring)
// KORREKTUR: Dieses Plugin ist entscheidend. Es schaltet den `springBoot`-Block
// und alle Spring-Boot-spezifischen Gradle-Tasks frei.
alias(libs.plugins.spring.boot)
// Dependency Management für konsistente Spring-Versionen
alias(libs.plugins.spring.dependencyManagement)
}
dependencies {
api(platform(projects.platform.platformBom))
implementation(projects.members.membersDomain)
implementation(projects.members.membersApplication)
implementation(projects.core.coreDomain)
implementation(projects.core.coreUtils)
implementation(projects.infrastructure.cache.cacheApi)
implementation(projects.infrastructure.eventStore.eventStoreApi)
implementation(projects.infrastructure.messaging.messagingClient)
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.postgresql:postgresql")
testImplementation(projects.platform.platformTesting)
}
@@ -0,0 +1,179 @@
package at.mocode.members.infrastructure.persistence
import at.mocode.core.utils.database.DatabaseFactory
import at.mocode.members.domain.model.Member
import at.mocode.members.domain.repository.MemberRepository
import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.springframework.stereotype.Repository
/**
* Database implementation of MemberRepository using Exposed ORM.
*/
@Repository
class MemberRepositoryImpl : MemberRepository {
override suspend fun findById(id: Uuid): Member? = DatabaseFactory.dbQuery {
MemberTable.selectAll().where { MemberTable.id eq id }
.map { rowToMember(it) }
.singleOrNull()
}
override suspend fun findByMembershipNumber(membershipNumber: String): Member? = DatabaseFactory.dbQuery {
MemberTable.selectAll().where { MemberTable.membershipNumber eq membershipNumber }
.map { rowToMember(it) }
.singleOrNull()
}
override suspend fun findByEmail(email: String): Member? = DatabaseFactory.dbQuery {
MemberTable.selectAll().where { MemberTable.email.lowerCase() eq email.lowercase() }
.map { rowToMember(it) }
.singleOrNull()
}
override suspend fun findByName(searchTerm: String, limit: Int): List<Member> = DatabaseFactory.dbQuery {
MemberTable.selectAll().where {
(MemberTable.firstName.lowerCase() like "%${searchTerm.lowercase()}%") or
(MemberTable.lastName.lowerCase() like "%${searchTerm.lowercase()}%")
}
.limit(limit)
.map { rowToMember(it) }
}
override suspend fun findAllActive(limit: Int, offset: Int): List<Member> = DatabaseFactory.dbQuery {
MemberTable.selectAll().where { MemberTable.isActive eq true }
.limit(limit, offset.toLong())
.map { rowToMember(it) }
}
override suspend fun findAll(limit: Int, offset: Int): List<Member> = DatabaseFactory.dbQuery {
MemberTable.selectAll()
.limit(limit, offset.toLong())
.map { rowToMember(it) }
}
override suspend fun findByMembershipStartDateRange(startDate: LocalDate, endDate: LocalDate): List<Member> = DatabaseFactory.dbQuery {
MemberTable.selectAll().where {
(MemberTable.membershipStartDate greaterEq startDate) and
(MemberTable.membershipStartDate lessEq endDate)
}
.map { rowToMember(it) }
}
override suspend fun findByMembershipEndDateRange(startDate: LocalDate, endDate: LocalDate): List<Member> = DatabaseFactory.dbQuery {
MemberTable.selectAll().where {
(MemberTable.membershipEndDate.isNotNull()) and
(MemberTable.membershipEndDate greaterEq startDate) and
(MemberTable.membershipEndDate lessEq endDate)
}
.map { rowToMember(it) }
}
override suspend fun findMembersWithExpiringMembership(daysAhead: Int): List<Member> = DatabaseFactory.dbQuery {
val currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
val futureDate = LocalDate(currentDate.year, currentDate.month, currentDate.dayOfMonth + daysAhead)
MemberTable.selectAll().where {
(MemberTable.membershipEndDate.isNotNull()) and
(MemberTable.membershipEndDate lessEq futureDate) and
(MemberTable.isActive eq true)
}
.map { rowToMember(it) }
}
override suspend fun save(member: Member): Member = DatabaseFactory.dbQuery {
val existingMember = MemberTable.selectAll().where { MemberTable.id eq member.memberId }.singleOrNull()
if (existingMember != null) {
// Update existing member
MemberTable.update({ MemberTable.id eq member.memberId }) {
it[firstName] = member.firstName
it[lastName] = member.lastName
it[email] = member.email
it[phone] = member.phone
it[dateOfBirth] = member.dateOfBirth
it[membershipNumber] = member.membershipNumber
it[membershipStartDate] = member.membershipStartDate
it[membershipEndDate] = member.membershipEndDate
it[isActive] = member.isActive
it[address] = member.address
it[emergencyContact] = member.emergencyContact
it[updatedAt] = Clock.System.now()
}
} else {
// Insert new member
MemberTable.insert {
it[id] = member.memberId
it[firstName] = member.firstName
it[lastName] = member.lastName
it[email] = member.email
it[phone] = member.phone
it[dateOfBirth] = member.dateOfBirth
it[membershipNumber] = member.membershipNumber
it[membershipStartDate] = member.membershipStartDate
it[membershipEndDate] = member.membershipEndDate
it[isActive] = member.isActive
it[address] = member.address
it[emergencyContact] = member.emergencyContact
}
}
member
}
override suspend fun delete(id: Uuid): Boolean = DatabaseFactory.dbQuery {
MemberTable.deleteWhere { MemberTable.id eq id } > 0
}
override suspend fun countActive(): Long = DatabaseFactory.dbQuery {
MemberTable.selectAll().where { MemberTable.isActive eq true }.count()
}
override suspend fun countAll(): Long = DatabaseFactory.dbQuery {
MemberTable.selectAll().count()
}
override suspend fun existsByMembershipNumber(membershipNumber: String, excludeMemberId: Uuid?): Boolean = DatabaseFactory.dbQuery {
val query = if (excludeMemberId != null) {
MemberTable.selectAll().where {
(MemberTable.membershipNumber eq membershipNumber) and
(MemberTable.id neq excludeMemberId)
}
} else {
MemberTable.selectAll().where { MemberTable.membershipNumber eq membershipNumber }
}
query.count() > 0
}
override suspend fun existsByEmail(email: String, excludeMemberId: Uuid?): Boolean = DatabaseFactory.dbQuery {
val query = if (excludeMemberId != null) {
MemberTable.selectAll().where {
(MemberTable.email.lowerCase() eq email.lowercase()) and
(MemberTable.id neq excludeMemberId)
}
} else {
MemberTable.selectAll().where { MemberTable.email.lowerCase() eq email.lowercase() }
}
query.count() > 0
}
private fun rowToMember(row: ResultRow): Member {
return Member(
memberId = row[MemberTable.id],
firstName = row[MemberTable.firstName],
lastName = row[MemberTable.lastName],
email = row[MemberTable.email],
phone = row[MemberTable.phone],
dateOfBirth = row[MemberTable.dateOfBirth],
membershipNumber = row[MemberTable.membershipNumber],
membershipStartDate = row[MemberTable.membershipStartDate],
membershipEndDate = row[MemberTable.membershipEndDate],
isActive = row[MemberTable.isActive],
address = row[MemberTable.address],
emergencyContact = row[MemberTable.emergencyContact]
)
}
}
@@ -0,0 +1,31 @@
package at.mocode.members.infrastructure.persistence
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.kotlin.datetime.date
import org.jetbrains.exposed.sql.kotlin.datetime.timestamp
import org.jetbrains.exposed.sql.kotlin.datetime.CurrentTimestamp
/**
* Database table definition for members in the member management context.
*
* This table stores member information including personal details,
* membership information, and contact details.
*/
object MemberTable : Table("members") {
val id = uuid("id").autoGenerate()
val firstName = varchar("first_name", 100)
val lastName = varchar("last_name", 100)
val email = varchar("email", 255).uniqueIndex()
val phone = varchar("phone", 50).nullable()
val dateOfBirth = date("date_of_birth").nullable()
val membershipNumber = varchar("membership_number", 50).uniqueIndex()
val membershipStartDate = date("membership_start_date")
val membershipEndDate = date("membership_end_date").nullable()
val isActive = bool("is_active").default(true)
val address = varchar("address", 500).nullable()
val emergencyContact = varchar("emergency_contact", 255).nullable()
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
val updatedAt = timestamp("updated_at").defaultExpression(CurrentTimestamp)
override val primaryKey = PrimaryKey(id)
}
@@ -0,0 +1,103 @@
package at.mocode.members.infrastructure.repository
import at.mocode.members.domain.model.Member
import at.mocode.members.domain.repository.MemberRepository
import com.benasher44.uuid.Uuid
import kotlinx.datetime.LocalDate
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.springframework.stereotype.Repository
import java.util.concurrent.ConcurrentHashMap
/**
* In-memory implementation of MemberRepository for development and testing purposes.
*/
@Repository
class InMemoryMemberRepository : MemberRepository {
private val members = ConcurrentHashMap<Uuid, Member>()
override suspend fun findById(id: Uuid): Member? {
return members[id]
}
override suspend fun findByMembershipNumber(membershipNumber: String): Member? {
return members.values.find { it.membershipNumber == membershipNumber }
}
override suspend fun findByEmail(email: String): Member? {
return members.values.find { it.email.equals(email, ignoreCase = true) }
}
override suspend fun findByName(searchTerm: String, limit: Int): List<Member> {
return members.values
.filter {
it.firstName.contains(searchTerm, ignoreCase = true) ||
it.lastName.contains(searchTerm, ignoreCase = true)
}
.take(limit)
}
override suspend fun findAllActive(limit: Int, offset: Int): List<Member> {
return members.values
.filter { it.isActive }
.drop(offset)
.take(limit)
}
override suspend fun findAll(limit: Int, offset: Int): List<Member> {
return members.values
.drop(offset)
.take(limit)
}
override suspend fun findByMembershipStartDateRange(startDate: LocalDate, endDate: LocalDate): List<Member> {
return members.values
.filter { it.membershipStartDate >= startDate && it.membershipStartDate <= endDate }
}
override suspend fun findByMembershipEndDateRange(startDate: LocalDate, endDate: LocalDate): List<Member> {
return members.values
.filter { member ->
member.membershipEndDate?.let { memberEndDate ->
memberEndDate >= startDate && memberEndDate <= endDate
} ?: false
}
}
override suspend fun findMembersWithExpiringMembership(daysAhead: Int): List<Member> {
// Simplified implementation - returns members with end dates set
return members.values
.filter { it.membershipEndDate != null }
}
override suspend fun save(member: Member): Member {
members[member.memberId] = member
return member
}
override suspend fun delete(id: Uuid): Boolean {
return members.remove(id) != null
}
override suspend fun countActive(): Long {
return members.values.count { it.isActive }.toLong()
}
override suspend fun countAll(): Long {
return members.size.toLong()
}
override suspend fun existsByMembershipNumber(membershipNumber: String, excludeMemberId: Uuid?): Boolean {
return members.values.any {
it.membershipNumber == membershipNumber && it.memberId != excludeMemberId
}
}
override suspend fun existsByEmail(email: String, excludeMemberId: Uuid?): Boolean {
return members.values.any {
it.email.equals(email, ignoreCase = true) && it.memberId != excludeMemberId
}
}
}