Umbau zu SCS
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package at.mocode.dto.base
|
||||
|
||||
import at.mocode.serializers.KotlinInstantSerializer
|
||||
import at.mocode.serializers.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Base DTO interface for all data transfer objects
|
||||
*/
|
||||
interface BaseDto
|
||||
|
||||
/**
|
||||
* Base DTO for entities with ID and timestamps
|
||||
*/
|
||||
@Serializable
|
||||
abstract class EntityDto : BaseDto {
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
abstract val id: Uuid
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
abstract val createdAt: Instant
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
abstract val updatedAt: Instant
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard API response wrapper
|
||||
*/
|
||||
@Serializable
|
||||
data class ApiResponse<T>(
|
||||
val success: Boolean,
|
||||
val data: T? = null,
|
||||
val error: ErrorDto? = null,
|
||||
val message: String? = null
|
||||
) : BaseDto
|
||||
|
||||
/**
|
||||
* Error information DTO
|
||||
*/
|
||||
@Serializable
|
||||
data class ErrorDto(
|
||||
val code: String,
|
||||
val message: String,
|
||||
val details: Map<String, String>? = null
|
||||
) : BaseDto
|
||||
|
||||
/**
|
||||
* Pagination information
|
||||
*/
|
||||
@Serializable
|
||||
data class PaginationDto(
|
||||
val page: Int,
|
||||
val size: Int,
|
||||
val total: Long,
|
||||
val totalPages: Int
|
||||
) : BaseDto
|
||||
|
||||
/**
|
||||
* Paginated response wrapper
|
||||
*/
|
||||
@Serializable
|
||||
data class PagedResponse<T>(
|
||||
val data: List<T>,
|
||||
val pagination: PaginationDto
|
||||
) : BaseDto
|
||||
@@ -0,0 +1,35 @@
|
||||
package at.mocode.enums
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Data source enumeration - indicates where data originated from
|
||||
*/
|
||||
@Serializable
|
||||
enum class DatenQuelleE { OEPS_ZNS, MANUELL }
|
||||
|
||||
/**
|
||||
* Horse gender enumeration
|
||||
*/
|
||||
@Serializable
|
||||
enum class PferdeGeschlechtE {
|
||||
HENGST, STUTE, WALLACH, UNBEKANNT
|
||||
}
|
||||
|
||||
/**
|
||||
* Person gender enumeration
|
||||
*/
|
||||
@Serializable
|
||||
enum class GeschlechtE { M, W, D, UNBEKANNT }
|
||||
|
||||
/**
|
||||
* Sport discipline enumeration
|
||||
*/
|
||||
@Serializable
|
||||
enum class SparteE { DRESSUR, SPRINGEN, VIELSEITIGKEIT, FAHREN, VOLTIGIEREN, WESTERN, DISTANZ, ISLAND, PFERDESPORT_SPIEL, BASIS, KOMBINIERT, SONSTIGES }
|
||||
|
||||
/**
|
||||
* Venue/place type enumeration
|
||||
*/
|
||||
@Serializable
|
||||
enum class PlatzTypE { AUSTRAGUNG, VORBEREITUNG, LONGIEREN, SONSTIGES }
|
||||
@@ -0,0 +1,51 @@
|
||||
package at.mocode.serializers
|
||||
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuidFrom
|
||||
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.LocalTime
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
object BigDecimalSerializer : KSerializer<BigDecimal> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: BigDecimal) = encoder.encodeString(value.toStringExpanded())
|
||||
override fun deserialize(decoder: Decoder): BigDecimal = BigDecimal.parseString(decoder.decodeString())
|
||||
}
|
||||
|
||||
object UuidSerializer : KSerializer<Uuid> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: Uuid) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): Uuid = uuidFrom(decoder.decodeString())
|
||||
}
|
||||
|
||||
object KotlinInstantSerializer : KSerializer<Instant> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): Instant = Instant.parse(decoder.decodeString())
|
||||
}
|
||||
|
||||
object KotlinLocalDateSerializer : KSerializer<LocalDate> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): LocalDate = LocalDate.parse(decoder.decodeString())
|
||||
}
|
||||
|
||||
object KotlinLocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime = LocalDateTime.parse(decoder.decodeString())
|
||||
}
|
||||
|
||||
object KotlinLocalTimeSerializer : KSerializer<LocalTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalTime", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: LocalTime) = encoder.encodeString(value.toString())
|
||||
override fun deserialize(decoder: Decoder): LocalTime = LocalTime.parse(decoder.decodeString())
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package at.mocode.validation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the result of a validation operation
|
||||
*/
|
||||
@Serializable
|
||||
sealed class ValidationResult {
|
||||
@Serializable
|
||||
object Valid : ValidationResult()
|
||||
|
||||
@Serializable
|
||||
data class Invalid(val errors: List<ValidationError>) : ValidationResult()
|
||||
|
||||
fun isValid(): Boolean = this is Valid
|
||||
fun isInvalid(): Boolean = this is Invalid
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single validation error
|
||||
*/
|
||||
@Serializable
|
||||
data class ValidationError(
|
||||
val field: String,
|
||||
val message: String,
|
||||
val code: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Exception thrown when validation fails
|
||||
*/
|
||||
class ValidationException(
|
||||
val validationResult: ValidationResult.Invalid
|
||||
) : IllegalArgumentException(
|
||||
"Validation failed: ${validationResult.errors.joinToString(", ") { "${it.field}: ${it.message}" }}"
|
||||
)
|
||||
@@ -0,0 +1,150 @@
|
||||
package at.mocode.validation
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.todayIn
|
||||
|
||||
/**
|
||||
* Common validation utilities
|
||||
*/
|
||||
object ValidationUtils {
|
||||
|
||||
/**
|
||||
* Validates that a string is not blank
|
||||
*/
|
||||
fun validateNotBlank(value: String?, fieldName: String): ValidationError? {
|
||||
return if (value.isNullOrBlank()) {
|
||||
ValidationError(fieldName, "$fieldName cannot be blank", "REQUIRED")
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates string length
|
||||
*/
|
||||
fun validateLength(value: String?, fieldName: String, maxLength: Int, minLength: Int = 0): ValidationError? {
|
||||
if (value == null) return null
|
||||
|
||||
return when {
|
||||
value.length < minLength -> ValidationError(
|
||||
fieldName,
|
||||
"$fieldName must be at least $minLength characters long",
|
||||
"MIN_LENGTH"
|
||||
)
|
||||
value.length > maxLength -> ValidationError(
|
||||
fieldName,
|
||||
"$fieldName cannot exceed $maxLength characters",
|
||||
"MAX_LENGTH"
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates email format
|
||||
*/
|
||||
fun validateEmail(email: String?, fieldName: String = "email"): ValidationError? {
|
||||
if (email.isNullOrBlank()) return null
|
||||
|
||||
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$".toRegex()
|
||||
return if (!emailRegex.matches(email)) {
|
||||
ValidationError(fieldName, "Invalid email format", "INVALID_FORMAT")
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates phone number format (basic validation)
|
||||
*/
|
||||
fun validatePhoneNumber(phone: String?, fieldName: String = "telefon"): ValidationError? {
|
||||
if (phone.isNullOrBlank()) return null
|
||||
|
||||
// Remove common separators and spaces
|
||||
val cleanPhone = phone.replace(Regex("[\\s\\-\\(\\)\\+]"), "")
|
||||
|
||||
return if (cleanPhone.length < 6 || cleanPhone.length > 20 || !cleanPhone.all { it.isDigit() }) {
|
||||
ValidationError(fieldName, "Invalid phone number format", "INVALID_FORMAT")
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates postal code format (basic validation for various countries)
|
||||
*/
|
||||
fun validatePostalCode(postalCode: String?, fieldName: String = "plz"): ValidationError? {
|
||||
if (postalCode.isNullOrBlank()) return null
|
||||
|
||||
// Basic validation: 3-10 alphanumeric characters
|
||||
return if (postalCode.length < 3 || postalCode.length > 10 || !postalCode.all { it.isLetterOrDigit() }) {
|
||||
ValidationError(fieldName, "Invalid postal code format", "INVALID_FORMAT")
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates 3-letter country code
|
||||
*/
|
||||
fun validateCountryCode(countryCode: String?, fieldName: String = "nationalitaet"): ValidationError? {
|
||||
if (countryCode.isNullOrBlank()) return null
|
||||
|
||||
return if (countryCode.length != 3 || !countryCode.all { it.isLetter() }) {
|
||||
ValidationError(fieldName, "Country code must be exactly 3 letters", "INVALID_FORMAT")
|
||||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates birth date
|
||||
*/
|
||||
fun validateBirthDate(birthDate: LocalDate?, fieldName: String = "geburtsdatum"): ValidationError? {
|
||||
if (birthDate == null) return null
|
||||
|
||||
val today = Clock.System.todayIn(TimeZone.currentSystemDefault())
|
||||
val minDate = LocalDate(1900, 1, 1)
|
||||
|
||||
return when {
|
||||
birthDate > today -> ValidationError(
|
||||
fieldName,
|
||||
"Birth date cannot be in the future",
|
||||
"FUTURE_DATE"
|
||||
)
|
||||
birthDate < minDate -> ValidationError(
|
||||
fieldName,
|
||||
"Birth date cannot be before year 1900",
|
||||
"INVALID_DATE"
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates year value
|
||||
*/
|
||||
fun validateYear(year: Int?, fieldName: String, minYear: Int = 1900): ValidationError? {
|
||||
if (year == null) return null
|
||||
|
||||
val currentYear = Clock.System.todayIn(TimeZone.currentSystemDefault()).year
|
||||
|
||||
return when {
|
||||
year < minYear -> ValidationError(
|
||||
fieldName,
|
||||
"Year cannot be before $minYear",
|
||||
"INVALID_YEAR"
|
||||
)
|
||||
year > currentYear + 10 -> ValidationError(
|
||||
fieldName,
|
||||
"Year cannot be more than 10 years in the future",
|
||||
"FUTURE_YEAR"
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates OEPS Satz number format (Austrian specific)
|
||||
*/
|
||||
fun validateOepsSatzNr(oepsSatzNr: String?, fieldName: String = "oepsSatzNr"): ValidationError? {
|
||||
if (oepsSatzNr.isNullOrBlank()) return null
|
||||
|
||||
// Basic validation: should be numeric and reasonable length
|
||||
return if (oepsSatzNr.length < 3 || oepsSatzNr.length > 20 || !oepsSatzNr.all { it.isDigit() }) {
|
||||
ValidationError(fieldName, "Invalid OEPS Satz number format", "INVALID_FORMAT")
|
||||
} else null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user