feature/entries
Reviewed-on: #2 Co-authored-by: Jari <scijar@gmail.com> Co-committed-by: Jari <scijar@gmail.com>
This commit is contained in:
parent
52033fe77f
commit
fc6ed7f959
|
|
@ -47,3 +47,4 @@ bin/
|
||||||
.idea/*
|
.idea/*
|
||||||
.env
|
.env
|
||||||
data/*
|
data/*
|
||||||
|
testSuite/*
|
||||||
|
|
@ -51,6 +51,9 @@ dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
kapt("org.springframework.boot:spring-boot-configuration-processor")
|
kapt("org.springframework.boot:spring-boot-configuration-processor")
|
||||||
|
|
||||||
|
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.0")
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
|
|
||||||
// implementation("io.jsonwebtoken:jjwt-api:0.12.6")
|
// implementation("io.jsonwebtoken:jjwt-api:0.12.6")
|
||||||
// runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
|
// runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6")
|
||||||
// runtimeOnly("io.jsonwebtoken:jjwt-gson:0.12.6")
|
// runtimeOnly("io.jsonwebtoken:jjwt-gson:0.12.6")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,38 @@
|
||||||
package org.octopus.internal.common.models
|
package org.octopus.internal.common.models
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonGetter
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import org.octopus.internal.common.enums.EEntryType
|
import org.octopus.internal.common.enums.EEntryType
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class Entry(
|
data class Entry(
|
||||||
|
@field:Schema(description = "The id of the entry in the DB")
|
||||||
var id: Long?,
|
var id: Long?,
|
||||||
|
@field:Schema(description = "The name of the entry (between 1 and 50 characters long).")
|
||||||
|
val name: String,
|
||||||
|
@field:Schema(description = "The type of entry (INCOME, OUTCOME, INVESTMENT).")
|
||||||
val type: EEntryType,
|
val type: EEntryType,
|
||||||
|
@field:Schema(description = "The amount of the entry (greater than 0.00).")
|
||||||
val amount: Double,
|
val amount: Double,
|
||||||
|
@field:Schema(description = "IF NOT recurrent: when is the entry being counted.")
|
||||||
val fixedDate: Date? = null,
|
val fixedDate: Date? = null,
|
||||||
|
@field:Schema(description = "IF recurrent: must be true")
|
||||||
val recurrent: Boolean? = false,
|
val recurrent: Boolean? = false,
|
||||||
|
@field:Schema(description = "IF recurrent: list of month in which the event is repeated (1=JAN, 12=DEC)")
|
||||||
|
@get:JsonIgnore
|
||||||
val recurrentMonths: List<Month>? = mutableListOf(),
|
val recurrentMonths: List<Month>? = mutableListOf(),
|
||||||
val endDate: Date? = null
|
@field:Schema(description = "IF recurrent: starting date of the repetition")
|
||||||
)
|
val startDate: Date? = null,
|
||||||
|
@field:Schema(description = "IF recurrent: ending date of the repetition")
|
||||||
|
val endDate: Date? = null,
|
||||||
|
@field:Schema(description = "Color of the entry in the UI")
|
||||||
|
val color: String
|
||||||
|
){
|
||||||
|
|
||||||
|
@JsonGetter("recurrentMonths")
|
||||||
|
fun getRecurrentMonthsAsInts(): List<Int>? {
|
||||||
|
return this.recurrentMonths?.map { it.value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.octopus.internal.db.converters
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter
|
||||||
|
import jakarta.persistence.Converter
|
||||||
|
import java.time.Month
|
||||||
|
|
||||||
|
@Converter
|
||||||
|
class MonthListConverter : AttributeConverter<List<Month>, String> {
|
||||||
|
|
||||||
|
override fun convertToDatabaseColumn(attribute: List<Month>?): String? {
|
||||||
|
return attribute?.joinToString(separator = ",") { it.name }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertToEntityAttribute(dbData: String?): List<Month>? {
|
||||||
|
return dbData?.split(",")
|
||||||
|
?.filter { it.isNotBlank() }
|
||||||
|
?.mapNotNull { runCatching { Month.valueOf(it.trim()) }.getOrNull() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package org.octopus.internal.db.entities
|
package org.octopus.internal.db.entities
|
||||||
|
|
||||||
|
import jakarta.persistence.Convert
|
||||||
import jakarta.persistence.Entity
|
import jakarta.persistence.Entity
|
||||||
import jakarta.persistence.GeneratedValue
|
import jakarta.persistence.GeneratedValue
|
||||||
import jakarta.persistence.GenerationType
|
import jakarta.persistence.GenerationType
|
||||||
import jakarta.persistence.Id
|
import jakarta.persistence.Id
|
||||||
import org.octopus.internal.common.enums.EEntryType
|
import org.octopus.internal.common.enums.EEntryType
|
||||||
|
import org.octopus.internal.db.converters.MonthListConverter
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
|
@ -12,10 +14,14 @@ import java.util.Date
|
||||||
data class EntryEntity(
|
data class EntryEntity(
|
||||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
var id: Long = 0L,
|
var id: Long = 0L,
|
||||||
|
val name: String,
|
||||||
val type: EEntryType,
|
val type: EEntryType,
|
||||||
val amount: Double,
|
val amount: Double,
|
||||||
val fixedDate: Date? = null,
|
val fixedDate: Date? = null,
|
||||||
val recurrent: Boolean? = false,
|
val recurrent: Boolean? = false,
|
||||||
|
@field:Convert(converter = MonthListConverter::class)
|
||||||
val recurrentMonths: List<Month>? = mutableListOf(),
|
val recurrentMonths: List<Month>? = mutableListOf(),
|
||||||
val endDate: Date? = null
|
val startDate: Date? = null,
|
||||||
|
val endDate: Date? = null,
|
||||||
|
val color: String
|
||||||
)
|
)
|
||||||
|
|
@ -13,4 +13,7 @@ interface EntryRepository {
|
||||||
fun getAllRecurrentOnAllMonths(): MutableList<Entry>
|
fun getAllRecurrentOnAllMonths(): MutableList<Entry>
|
||||||
fun getAllFixedUpToEndDate(endDate: Date): MutableList<Entry>
|
fun getAllFixedUpToEndDate(endDate: Date): MutableList<Entry>
|
||||||
fun getAllByType(type: EEntryType): MutableList<Entry>
|
fun getAllByType(type: EEntryType): MutableList<Entry>
|
||||||
|
|
||||||
|
fun deleteById(id: Long): Boolean
|
||||||
|
fun deleteAll(): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,17 @@ class EntryRepositoryImpl(
|
||||||
return mapper.toModels(jpa.findAllByType(type))
|
return mapper.toModels(jpa.findAllByType(type))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun deleteById(id: Long): Boolean {
|
||||||
|
jpa.deleteById(id)
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteAll(): Boolean {
|
||||||
|
jpa.deleteAll()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAllRecurrentMonthlyToEndDate(endDate: Date): MutableList<Entry> {
|
override fun getAllRecurrentMonthlyToEndDate(endDate: Date): MutableList<Entry> {
|
||||||
return mapper.toModels(
|
return mapper.toModels(
|
||||||
jpa.findAllByFixedDateIsNullAndRecurrentIsTrueAndEndDateIsNotNullAndEndDateBefore(endDate)
|
jpa.findAllByFixedDateIsNullAndRecurrentIsTrueAndEndDateIsNotNullAndEndDateBefore(endDate)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import org.octopus.internal.common.models.Entry
|
||||||
import org.octopus.internal.web.utils.dtos.EntryDto
|
import org.octopus.internal.web.utils.dtos.EntryDto
|
||||||
|
|
||||||
interface EntryService {
|
interface EntryService {
|
||||||
fun createEntry(entry: EntryDto)
|
fun createEntry(entry: EntryDto): Entry
|
||||||
fun getAllEntries(): MutableList<Entry>
|
fun getAllEntries(): MutableList<Entry>
|
||||||
|
fun deleteAll(): Boolean
|
||||||
|
fun deleteById(id: Long): Boolean
|
||||||
|
fun editEntry(id: Long, entry: EntryDto): Entry
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,82 @@
|
||||||
package org.octopus.internal.services.impl
|
package org.octopus.internal.services.impl
|
||||||
|
|
||||||
|
import org.octopus.internal.common.enums.EBusinessException
|
||||||
import org.octopus.internal.common.enums.EEntryType
|
import org.octopus.internal.common.enums.EEntryType
|
||||||
|
import org.octopus.internal.common.exceptions.OctopusPlanningException
|
||||||
import org.octopus.internal.common.models.Entry
|
import org.octopus.internal.common.models.Entry
|
||||||
import org.octopus.internal.repositories.EntryRepository
|
import org.octopus.internal.repositories.EntryRepository
|
||||||
import org.octopus.internal.services.EntryService
|
import org.octopus.internal.services.EntryService
|
||||||
import org.octopus.internal.web.utils.dtos.EntryDto
|
import org.octopus.internal.web.utils.dtos.EntryDto
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.Month
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class EntryServiceImpl(val repo: EntryRepository) : EntryService {
|
class EntryServiceImpl(val repo: EntryRepository) : EntryService {
|
||||||
override fun createEntry(entry: EntryDto) {
|
override fun createEntry(entry: EntryDto): Entry {
|
||||||
TODO("Not yet implemented")
|
if (!verifyDto(entry)) {
|
||||||
|
throw OctopusPlanningException.create(EBusinessException.INVALID_REQUEST, entry, "Entry must be either recurring or fixed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.createEntry(Entry(
|
||||||
|
id = 0L,
|
||||||
|
name = entry.name,
|
||||||
|
type = EEntryType.valueOf(entry.type),
|
||||||
|
amount = entry.amount,
|
||||||
|
fixedDate = entry.fixedDate,
|
||||||
|
startDate = entry.startDate,
|
||||||
|
endDate = entry.endDate,
|
||||||
|
recurrent = entry.recurrent,
|
||||||
|
recurrentMonths = entry.recurrentMonths?.map { m -> Month.of(m) }?.toList() ?: emptyList(),
|
||||||
|
color = entry.color
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllEntries(): MutableList<Entry> {
|
override fun getAllEntries(): MutableList<Entry> {
|
||||||
TODO("Not yet implemented")
|
val income = repo.getAllByType(EEntryType.INCOME)
|
||||||
|
val outcome = repo.getAllByType(EEntryType.OUTCOME)
|
||||||
|
val invest = repo.getAllByType(EEntryType.INVESTMENT)
|
||||||
|
return (income + outcome + invest).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteAll(): Boolean {
|
||||||
|
return repo.deleteAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteById(id: Long): Boolean {
|
||||||
|
return repo.deleteById(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun editEntry(id: Long, entry: EntryDto): Entry {
|
||||||
|
if (!verifyDto(entry)) {
|
||||||
|
throw OctopusPlanningException.create(EBusinessException.INVALID_REQUEST, entry, "Entry must be either recurring or fixed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo.createEntry(Entry(
|
||||||
|
id = id,
|
||||||
|
name = entry.name,
|
||||||
|
type = EEntryType.valueOf(entry.type),
|
||||||
|
amount = entry.amount,
|
||||||
|
fixedDate = entry.fixedDate,
|
||||||
|
startDate = entry.startDate,
|
||||||
|
endDate = entry.endDate,
|
||||||
|
recurrent = entry.recurrent,
|
||||||
|
recurrentMonths = entry.recurrentMonths?.map { m -> Month.of(m) }?.toList() ?: emptyList(),
|
||||||
|
color = entry.color
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyDto(entry: EntryDto): Boolean {
|
||||||
|
val eventIsRecurring = entry.fixedDate == null &&
|
||||||
|
entry.startDate != null &&
|
||||||
|
entry.endDate != null &&
|
||||||
|
entry.recurrent == true &&
|
||||||
|
entry.recurrentMonths?.isNotEmpty() ?: false
|
||||||
|
val eventIsFixed = entry.fixedDate != null &&
|
||||||
|
entry.startDate == null &&
|
||||||
|
entry.endDate == null &&
|
||||||
|
entry.recurrent != true &&
|
||||||
|
entry.recurrentMonths?.isEmpty() ?: true
|
||||||
|
return eventIsRecurring || eventIsFixed
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,23 +1,86 @@
|
||||||
package org.octopus.internal.web.controllers
|
package org.octopus.internal.web.controllers
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag
|
||||||
|
import jakarta.validation.Valid
|
||||||
import lombok.AllArgsConstructor
|
import lombok.AllArgsConstructor
|
||||||
import org.octopus.internal.common.models.Entry
|
import org.octopus.internal.common.models.Entry
|
||||||
import org.octopus.internal.services.EntryService
|
import org.octopus.internal.services.EntryService
|
||||||
|
import org.octopus.internal.web.utils.dtos.EntryDto
|
||||||
import org.octopus.internal.web.utils.responses.WebResponse
|
import org.octopus.internal.web.utils.responses.WebResponse
|
||||||
|
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/entries")
|
@RequestMapping("/entries")
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@Tag(name = "Entries Management", description = "Operations related to entries.")
|
||||||
class EntryController(
|
class EntryController(
|
||||||
val entryService: EntryService
|
val entryService: EntryService
|
||||||
) {
|
) {
|
||||||
|
@Operation(
|
||||||
|
summary = "Create a new planning entry",
|
||||||
|
description = "Adds a new income, outcome, or investment entry to the system.",
|
||||||
|
responses = [
|
||||||
|
ApiResponse(responseCode = "200", description = "Entry created successfully. Returned with ID")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@PostMapping
|
||||||
|
fun createEntry(
|
||||||
|
@Valid @RequestBody entryDto: EntryDto
|
||||||
|
): WebResponse<Entry> {
|
||||||
|
return WebResponse.ok(entryService.createEntry(entryDto))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get all entries in the DB",
|
||||||
|
description = "Returns a list containing all entries.",
|
||||||
|
responses = [
|
||||||
|
ApiResponse(responseCode = "200", description = "A valid list.")
|
||||||
|
]
|
||||||
|
)
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getAllClients(): WebResponse<List<Entry>> {
|
fun getAllEntries(): WebResponse<List<Entry>> {
|
||||||
return WebResponse.ok(entryService.getAllEntries())
|
return WebResponse.ok(entryService.getAllEntries())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Delete all entries in the DB",
|
||||||
|
description = "Returns the result of the operation.",
|
||||||
|
responses = [
|
||||||
|
ApiResponse(responseCode = "200", description = "Whether the operation succeeded.")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@DeleteMapping
|
||||||
|
fun deleteAllEntries(): WebResponse<Boolean> {
|
||||||
|
return WebResponse.ok(entryService.deleteAll())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Delete a specific entry in the DB",
|
||||||
|
description = "Returns the result of the operation.",
|
||||||
|
responses = [
|
||||||
|
ApiResponse(responseCode = "200", description = "Whether the operation succeeded.")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
fun deleteEntryById(@PathVariable("id") id: Long): WebResponse<Boolean> {
|
||||||
|
return WebResponse.ok(entryService.deleteById(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Updates a specific entry in the DB",
|
||||||
|
description = "Returns the updated entry.",
|
||||||
|
responses = [
|
||||||
|
ApiResponse(responseCode = "200", description = "The updated entry.")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
fun editEntryById(@PathVariable("id") id: Long,
|
||||||
|
@Valid @RequestBody entryDto: EntryDto): WebResponse<Entry> {
|
||||||
|
return WebResponse.ok(entryService.editEntry(id, entryDto))
|
||||||
|
}
|
||||||
|
|
||||||
// @GetMapping("/{id}")
|
// @GetMapping("/{id}")
|
||||||
// fun getClient(@PathVariable("id") id: String, @RequestParam(
|
// fun getClient(@PathVariable("id") id: String, @RequestParam(
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,7 @@ class BaseAdvice {
|
||||||
): WebResponse<Unit> {
|
): WebResponse<Unit> {
|
||||||
return WebResponse.ko(
|
return WebResponse.ko(
|
||||||
HttpStatus.NOT_ACCEPTABLE,
|
HttpStatus.NOT_ACCEPTABLE,
|
||||||
"${HttpStatus.NOT_ACCEPTABLE.reasonPhrase}: JSON parse error"
|
"${HttpStatus.NOT_ACCEPTABLE.reasonPhrase}: JSON parse error. Please verify the body has the correct format.",
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,43 @@
|
||||||
package org.octopus.internal.web.utils.dtos
|
package org.octopus.internal.web.utils.dtos
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
|
import jakarta.validation.constraints.DecimalMin
|
||||||
|
import org.hibernate.validator.constraints.Length
|
||||||
|
import org.octopus.internal.web.utils.dtos.validators.EntryHexColorValidator
|
||||||
import org.octopus.internal.web.utils.dtos.validators.EntryMonthValidator
|
import org.octopus.internal.web.utils.dtos.validators.EntryMonthValidator
|
||||||
import org.octopus.internal.web.utils.dtos.validators.EntryTypeValidator
|
import org.octopus.internal.web.utils.dtos.validators.EntryTypeValidator
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
data class EntryDto(
|
data class EntryDto(
|
||||||
|
@field:Schema(description = "The name of the entry (between 1 and 50 characters long).")
|
||||||
|
@field:Length(min = 1, max = 50, message = "The name must be between 1 and 50 characters long.")
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
@field:Schema(description = "The type of entry (INCOME, OUTCOME, INVESTMENT).")
|
||||||
@field:EntryTypeValidator.Validate
|
@field:EntryTypeValidator.Validate
|
||||||
val type: String,
|
val type: String,
|
||||||
|
|
||||||
|
@field:Schema(description = "The amount of the entry (greater than 0.00).")
|
||||||
|
@field:DecimalMin(value="0.00", inclusive = false, message = "The amount must be greater than 0.00.")
|
||||||
val amount: Double,
|
val amount: Double,
|
||||||
|
|
||||||
|
@field:Schema(description = "IF NOT recurrent: when is the entry being counted.")
|
||||||
val fixedDate: Date?,
|
val fixedDate: Date?,
|
||||||
|
|
||||||
|
@field:Schema(description = "IF recurrent: must be true")
|
||||||
val recurrent: Boolean?,
|
val recurrent: Boolean?,
|
||||||
|
|
||||||
|
@field:Schema(description = "IF recurrent: list of month in which the event is repeated (1=JAN, 12=DEC)")
|
||||||
@field:EntryMonthValidator.Validate
|
@field:EntryMonthValidator.Validate
|
||||||
val recurrentMonths: List<Int>?,
|
val recurrentMonths: List<Int>?,
|
||||||
val endDate: Date?
|
|
||||||
|
|
||||||
|
@field:Schema(description = "IF recurrent: starting date of the repetition")
|
||||||
|
val startDate: Date?,
|
||||||
|
|
||||||
|
@field:Schema(description = "IF recurrent: ending date of the repetition")
|
||||||
|
val endDate: Date?,
|
||||||
|
|
||||||
|
@field:Schema(description = "Color of the entry in the UI")
|
||||||
|
@field:EntryHexColorValidator.Validate
|
||||||
|
val color: String = "0xFFFFFF"
|
||||||
)
|
)
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.octopus.internal.web.utils.dtos.validators
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint
|
||||||
|
import jakarta.validation.ConstraintValidator
|
||||||
|
import jakarta.validation.ConstraintValidatorContext
|
||||||
|
import jakarta.validation.Payload
|
||||||
|
import org.octopus.internal.common.enums.EEntryType
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
|
||||||
|
class EntryHexColorValidator : ConstraintValidator<EntryHexColorValidator.Validate, String> {
|
||||||
|
companion object {
|
||||||
|
private val HEX_COLOR_PATTERN = Regex("^0x([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isValid(value: String?, context: ConstraintValidatorContext): Boolean {
|
||||||
|
if (value.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val isMatched = HEX_COLOR_PATTERN.matches(value)
|
||||||
|
|
||||||
|
if (!isMatched) {
|
||||||
|
context.disableDefaultConstraintViolation()
|
||||||
|
context.buildConstraintViolationWithTemplate("Invalid hex color format. Value must be in 0xRRGGBB format.")
|
||||||
|
.addConstraintViolation()
|
||||||
|
}
|
||||||
|
|
||||||
|
return isMatched
|
||||||
|
}
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@MustBeDocumented
|
||||||
|
@Constraint(validatedBy = [EntryHexColorValidator::class])
|
||||||
|
annotation class Validate(
|
||||||
|
val message: String = "",
|
||||||
|
val groups: Array<KClass<*>> = [],
|
||||||
|
val payload: Array<KClass<out Payload>> = []
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue