Clean routes
This commit is contained in:
parent
a7c832236c
commit
02c6b2a0c5
@ -9,7 +9,7 @@
|
|||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="me.liuwj.ktorm.database" level="INFO"/>
|
<logger name="me.liuwj.ktorm.database" level="DEBUG"/>
|
||||||
<logger name="com.zaxxer.hikari" level="INFO"/>
|
<logger name="com.zaxxer.hikari" level="INFO"/>
|
||||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||||
<logger name="io.netty" level="INFO"/>
|
<logger name="io.netty" level="INFO"/>
|
||||||
|
|||||||
@ -3,11 +3,15 @@ package be.vandewalleh
|
|||||||
import be.vandewalleh.auth.AuthenticationModule
|
import be.vandewalleh.auth.AuthenticationModule
|
||||||
import be.vandewalleh.auth.SimpleJWT
|
import be.vandewalleh.auth.SimpleJWT
|
||||||
import be.vandewalleh.extensions.ApplicationBuilder
|
import be.vandewalleh.extensions.ApplicationBuilder
|
||||||
|
import be.vandewalleh.extensions.RoutingBuilder
|
||||||
import be.vandewalleh.factories.configurationFactory
|
import be.vandewalleh.factories.configurationFactory
|
||||||
import be.vandewalleh.factories.dataSourceFactory
|
import be.vandewalleh.factories.dataSourceFactory
|
||||||
import be.vandewalleh.factories.databaseFactory
|
import be.vandewalleh.factories.databaseFactory
|
||||||
import be.vandewalleh.factories.simpleJwtFactory
|
import be.vandewalleh.factories.simpleJwtFactory
|
||||||
import be.vandewalleh.features.*
|
import be.vandewalleh.features.*
|
||||||
|
import be.vandewalleh.routing.NoteRoutes
|
||||||
|
import be.vandewalleh.routing.TagRoutes
|
||||||
|
import be.vandewalleh.routing.UserRoutes
|
||||||
import be.vandewalleh.services.NoteService
|
import be.vandewalleh.services.NoteService
|
||||||
import be.vandewalleh.services.UserService
|
import be.vandewalleh.services.UserService
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
@ -28,6 +32,19 @@ val mainModule = Kodein.Module("main") {
|
|||||||
bind<ApplicationBuilder>().inSet() with singleton { MigrationHook(instance()) }
|
bind<ApplicationBuilder>().inSet() with singleton { MigrationHook(instance()) }
|
||||||
bind<ApplicationBuilder>().inSet() with singleton { ShutdownDatabaseConnection(instance()) }
|
bind<ApplicationBuilder>().inSet() with singleton { ShutdownDatabaseConnection(instance()) }
|
||||||
|
|
||||||
|
bind() from setBinding<RoutingBuilder>()
|
||||||
|
bind<RoutingBuilder>().inSet() with singleton { NoteRoutes(instance()) }
|
||||||
|
bind<RoutingBuilder>().inSet() with singleton { TagRoutes(instance()) }
|
||||||
|
bind<RoutingBuilder>().inSet() with singleton {
|
||||||
|
UserRoutes(
|
||||||
|
instance(tag = "auth"),
|
||||||
|
instance(tag = "refresh"),
|
||||||
|
instance(),
|
||||||
|
instance()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bind<SimpleJWT>(tag = "auth") with singleton { simpleJwtFactory(instance<Config>().jwt.auth) }
|
bind<SimpleJWT>(tag = "auth") with singleton { simpleJwtFactory(instance<Config>().jwt.auth) }
|
||||||
bind<SimpleJWT>(tag = "refresh") with singleton { simpleJwtFactory(instance<Config>().jwt.refresh) }
|
bind<SimpleJWT>(tag = "refresh") with singleton { simpleJwtFactory(instance<Config>().jwt.refresh) }
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
package be.vandewalleh
|
package be.vandewalleh
|
||||||
|
|
||||||
import be.vandewalleh.extensions.ApplicationBuilder
|
import be.vandewalleh.extensions.ApplicationBuilder
|
||||||
import be.vandewalleh.routing.noteRoutes
|
import be.vandewalleh.extensions.RoutingBuilder
|
||||||
import be.vandewalleh.routing.tagsRoute
|
|
||||||
import be.vandewalleh.routing.userRoutes
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.auth.*
|
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
import io.ktor.server.netty.*
|
import io.ktor.server.netty.*
|
||||||
@ -43,7 +40,7 @@ fun serve(kodein: Kodein) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
with(embeddedServer(Netty, env)) {
|
with(embeddedServer(Netty, env)) {
|
||||||
addShutdownHook { stop(3, 5, TimeUnit.SECONDS) }
|
addShutdownHook { stop(1, 5, TimeUnit.SECONDS) }
|
||||||
start(wait = true)
|
start(wait = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,18 +53,8 @@ fun Application.module(kodein: Kodein) {
|
|||||||
it.builder(this)
|
it.builder(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
routing {
|
val routingBuilders: Set<RoutingBuilder> by kodein.instance()
|
||||||
route("/user") {
|
routingBuilders.forEach {
|
||||||
userRoutes(kodein)
|
routing(it.builder)
|
||||||
}
|
}
|
||||||
authenticate {
|
|
||||||
route("/notes") {
|
|
||||||
noteRoutes(kodein)
|
|
||||||
}
|
|
||||||
route("/tags") {
|
|
||||||
tagsRoute(kodein)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing
|
||||||
|
|
||||||
|
import be.vandewalleh.extensions.RoutingBuilder
|
||||||
import be.vandewalleh.extensions.authenticatedUserId
|
import be.vandewalleh.extensions.authenticatedUserId
|
||||||
import be.vandewalleh.extensions.respondStatus
|
import be.vandewalleh.extensions.respondStatus
|
||||||
import be.vandewalleh.features.ValidationException
|
import be.vandewalleh.features.ValidationException
|
||||||
@ -7,52 +8,62 @@ import be.vandewalleh.services.NoteService
|
|||||||
import be.vandewalleh.validation.noteValidator
|
import be.vandewalleh.validation.noteValidator
|
||||||
import be.vandewalleh.validation.receiveValidated
|
import be.vandewalleh.validation.receiveValidated
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
|
import io.ktor.auth.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.response.*
|
import io.ktor.response.*
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import org.kodein.di.Kodein
|
|
||||||
import org.kodein.di.generic.instance
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun Route.noteRoutes(kodein: Kodein) {
|
class NoteRoutes(noteService: NoteService) : RoutingBuilder({
|
||||||
val noteService by kodein.instance<NoteService>()
|
authenticate {
|
||||||
|
route("/notes") {
|
||||||
|
createNote(noteService)
|
||||||
|
getAllNotes(noteService)
|
||||||
|
|
||||||
|
route("/{uuid}") {
|
||||||
|
getNote(noteService)
|
||||||
|
updateNote(noteService)
|
||||||
|
deleteNote(noteService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
private fun Route.createNote(noteService: NoteService) {
|
||||||
post {
|
post {
|
||||||
val userId = call.authenticatedUserId()
|
val userId = call.authenticatedUserId()
|
||||||
val note = call.receiveValidated(noteValidator)
|
val note = call.receiveValidated(noteValidator)
|
||||||
val createdNote = noteService.create(userId, note)
|
val createdNote = noteService.create(userId, note)
|
||||||
call.respond(HttpStatusCode.Created, createdNote)
|
call.respond(HttpStatusCode.Created, createdNote)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.getAllNotes(noteService: NoteService) {
|
||||||
get {
|
get {
|
||||||
val userId = call.authenticatedUserId()
|
val userId = call.authenticatedUserId()
|
||||||
val notes = noteService.findAll(userId)
|
val limit = call.parameters["limit"]?.toInt() ?: 20// FIXME validate
|
||||||
|
val after = call.parameters["after"]?.let { UUID.fromString(it) } // FIXME validate
|
||||||
|
val notes = noteService.findAll(userId, limit, after)
|
||||||
call.respond(notes)
|
call.respond(notes)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
route("/{uuid}") {
|
private fun Route.getNote(noteService: NoteService) {
|
||||||
|
|
||||||
fun ApplicationCall.userIdNoteIdPair(): Pair<Int, UUID> {
|
|
||||||
val userId = authenticatedUserId()
|
|
||||||
val uuid = parameters["uuid"]
|
|
||||||
val noteUuid = try {
|
|
||||||
UUID.fromString(uuid)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
throw ValidationException("`$uuid` is not a valid UUID")
|
|
||||||
}
|
|
||||||
return userId to noteUuid
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
get {
|
||||||
val (userId, noteUuid) = call.userIdNoteIdPair()
|
val userId = call.authenticatedUserId()
|
||||||
|
val noteUuid = call.noteUuid()
|
||||||
|
|
||||||
val response = noteService.find(userId, noteUuid)
|
val response = noteService.find(userId, noteUuid)
|
||||||
?: return@get call.respondStatus(HttpStatusCode.NotFound)
|
?: return@get call.respondStatus(HttpStatusCode.NotFound)
|
||||||
call.respond(response)
|
call.respond(response)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.updateNote(noteService: NoteService) {
|
||||||
put {
|
put {
|
||||||
val (userId, noteUuid) = call.userIdNoteIdPair()
|
val userId = call.authenticatedUserId()
|
||||||
|
val noteUuid = call.noteUuid()
|
||||||
|
|
||||||
val note = call.receiveValidated(noteValidator)
|
val note = call.receiveValidated(noteValidator)
|
||||||
note.uuid = noteUuid
|
note.uuid = noteUuid
|
||||||
@ -61,15 +72,25 @@ fun Route.noteRoutes(kodein: Kodein) {
|
|||||||
call.respondStatus(HttpStatusCode.OK)
|
call.respondStatus(HttpStatusCode.OK)
|
||||||
else call.respondStatus(HttpStatusCode.NotFound)
|
else call.respondStatus(HttpStatusCode.NotFound)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.deleteNote(noteService: NoteService) {
|
||||||
delete {
|
delete {
|
||||||
val (userId, noteUuid) = call.userIdNoteIdPair()
|
val userId = call.authenticatedUserId()
|
||||||
|
val noteUuid = call.noteUuid()
|
||||||
|
|
||||||
if (noteService.delete(userId, noteUuid))
|
if (noteService.delete(userId, noteUuid))
|
||||||
call.respondStatus(HttpStatusCode.OK)
|
call.respondStatus(HttpStatusCode.OK)
|
||||||
else
|
else
|
||||||
call.respondStatus(HttpStatusCode.NotFound)
|
call.respondStatus(HttpStatusCode.NotFound)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ApplicationCall.noteUuid(): UUID {
|
||||||
|
val uuid = parameters["uuid"]
|
||||||
|
return try {
|
||||||
|
UUID.fromString(uuid)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw ValidationException("`$uuid` is not a valid UUID")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing
|
||||||
|
|
||||||
|
import be.vandewalleh.extensions.RoutingBuilder
|
||||||
import be.vandewalleh.extensions.authenticatedUserId
|
import be.vandewalleh.extensions.authenticatedUserId
|
||||||
import be.vandewalleh.services.NoteService
|
import be.vandewalleh.services.NoteService
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
|
import io.ktor.auth.*
|
||||||
import io.ktor.response.*
|
import io.ktor.response.*
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
fun Route.tagsRoute(kodein: Kodein) {
|
class TagRoutes(noteService: NoteService) : RoutingBuilder({
|
||||||
val noteService by kodein.instance<NoteService>()
|
authenticate {
|
||||||
|
get("/tags") {
|
||||||
get {
|
|
||||||
call.respond(noteService.getTags(call.authenticatedUserId()))
|
call.respond(noteService.getTags(call.authenticatedUserId()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package be.vandewalleh.routing
|
package be.vandewalleh.routing
|
||||||
|
|
||||||
import be.vandewalleh.auth.SimpleJWT
|
import be.vandewalleh.auth.SimpleJWT
|
||||||
import be.vandewalleh.auth.UserIdPrincipal
|
|
||||||
import be.vandewalleh.auth.UsernamePasswordCredential
|
import be.vandewalleh.auth.UsernamePasswordCredential
|
||||||
|
import be.vandewalleh.extensions.RoutingBuilder
|
||||||
import be.vandewalleh.extensions.authenticatedUserId
|
import be.vandewalleh.extensions.authenticatedUserId
|
||||||
import be.vandewalleh.extensions.respondStatus
|
import be.vandewalleh.extensions.respondStatus
|
||||||
import be.vandewalleh.features.PasswordHash
|
import be.vandewalleh.features.PasswordHash
|
||||||
@ -16,18 +16,51 @@ import io.ktor.http.*
|
|||||||
import io.ktor.request.*
|
import io.ktor.request.*
|
||||||
import io.ktor.response.*
|
import io.ktor.response.*
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import org.kodein.di.Kodein
|
|
||||||
import org.kodein.di.generic.instance
|
|
||||||
|
|
||||||
data class RefreshToken(val refreshToken: String)
|
class UserRoutes(
|
||||||
data class DualToken(val token: String, val refreshToken: String)
|
authJWT: SimpleJWT,
|
||||||
|
refreshJWT: SimpleJWT,
|
||||||
|
userService: UserService,
|
||||||
|
passwordHash: PasswordHash
|
||||||
|
) : RoutingBuilder({
|
||||||
|
route("/user") {
|
||||||
|
createUser(userService)
|
||||||
|
route("/login") {
|
||||||
|
login(userService, passwordHash, authJWT, refreshJWT)
|
||||||
|
}
|
||||||
|
route("/refresh_token") {
|
||||||
|
refreshToken(userService, authJWT, refreshJWT)
|
||||||
|
}
|
||||||
|
authenticate {
|
||||||
|
deleteUser(userService)
|
||||||
|
route("/me") {
|
||||||
|
userInfo(userService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
fun Route.userRoutes(kodein: Kodein) {
|
private fun Route.userInfo(userService: UserService) {
|
||||||
val authSimpleJwt by kodein.instance<SimpleJWT>("auth")
|
get {
|
||||||
val refreshSimpleJwt by kodein.instance<SimpleJWT>("refresh")
|
val id = call.authenticatedUserId()
|
||||||
val userService by kodein.instance<UserService>()
|
val info = userService.find(id)
|
||||||
val passwordHash by kodein.instance<PasswordHash>()
|
if (info != null) call.respond(mapOf("user" to info))
|
||||||
|
else call.respondStatus(HttpStatusCode.Unauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Route.deleteUser(userService: UserService) {
|
||||||
|
delete {
|
||||||
|
val userId = call.authenticatedUserId()
|
||||||
|
call.respondStatus(
|
||||||
|
if (userService.delete(userId)) HttpStatusCode.OK
|
||||||
|
else HttpStatusCode.NotFound
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Route.createUser(userService: UserService) {
|
||||||
post {
|
post {
|
||||||
val user = call.receiveValidated(registerValidator)
|
val user = call.receiveValidated(registerValidator)
|
||||||
|
|
||||||
@ -39,8 +72,16 @@ fun Route.userRoutes(kodein: Kodein) {
|
|||||||
|
|
||||||
call.respond(HttpStatusCode.Created, newUser)
|
call.respond(HttpStatusCode.Created, newUser)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
post("/login") {
|
|
||||||
|
private fun Route.login(
|
||||||
|
userService: UserService,
|
||||||
|
passwordHash: PasswordHash,
|
||||||
|
authJWT: SimpleJWT,
|
||||||
|
refreshJWT: SimpleJWT
|
||||||
|
) {
|
||||||
|
post {
|
||||||
val credential = call.receive<UsernamePasswordCredential>()
|
val credential = call.receive<UsernamePasswordCredential>()
|
||||||
|
|
||||||
val user = userService.find(credential.username)
|
val user = userService.find(credential.username)
|
||||||
@ -51,17 +92,20 @@ fun Route.userRoutes(kodein: Kodein) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val response = DualToken(
|
val response = DualToken(
|
||||||
token = authSimpleJwt.sign(user.id),
|
token = authJWT.sign(user.id),
|
||||||
refreshToken = refreshSimpleJwt.sign(user.id)
|
refreshToken = refreshJWT.sign(user.id)
|
||||||
)
|
)
|
||||||
return@post call.respond(response)
|
return@post call.respond(response)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
post("/refresh_token") {
|
|
||||||
|
private fun Route.refreshToken(userService: UserService, authJWT: SimpleJWT, refreshJWT: SimpleJWT) {
|
||||||
|
post {
|
||||||
val token = call.receive<RefreshToken>().refreshToken
|
val token = call.receive<RefreshToken>().refreshToken
|
||||||
|
|
||||||
val id = try {
|
val id = try {
|
||||||
val decodedJWT = refreshSimpleJwt.verifier.verify(token)
|
val decodedJWT = refreshJWT.verifier.verify(token)
|
||||||
decodedJWT.getClaim("id").asInt()
|
decodedJWT.getClaim("id").asInt()
|
||||||
} catch (e: JWTVerificationException) {
|
} catch (e: JWTVerificationException) {
|
||||||
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||||
@ -71,26 +115,12 @@ fun Route.userRoutes(kodein: Kodein) {
|
|||||||
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
return@post call.respondStatus(HttpStatusCode.Unauthorized)
|
||||||
|
|
||||||
val response = DualToken(
|
val response = DualToken(
|
||||||
token = authSimpleJwt.sign(id),
|
token = authJWT.sign(id),
|
||||||
refreshToken = refreshSimpleJwt.sign(id)
|
refreshToken = refreshJWT.sign(id)
|
||||||
)
|
)
|
||||||
return@post call.respond(response)
|
return@post call.respond(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticate {
|
|
||||||
delete {
|
|
||||||
val userId = call.authenticatedUserId()
|
|
||||||
call.respondStatus(
|
|
||||||
if (userService.delete(userId)) HttpStatusCode.OK
|
|
||||||
else HttpStatusCode.NotFound
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
get("/me") {
|
|
||||||
val id = call.principal<UserIdPrincipal>()!!.id
|
|
||||||
val info = userService.find(id)
|
|
||||||
if (info != null) call.respond(mapOf("user" to info))
|
|
||||||
else call.respondStatus(HttpStatusCode.Unauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class RefreshToken(val refreshToken: String)
|
||||||
|
private data class DualToken(val token: String, val refreshToken: String)
|
||||||
|
|||||||
@ -18,12 +18,25 @@ class NoteService(private val db: Database) {
|
|||||||
/**
|
/**
|
||||||
* returns a list of [Note] associated with the userId
|
* returns a list of [Note] associated with the userId
|
||||||
*/
|
*/
|
||||||
suspend fun findAll(userId: Int, limit: Int = 20, offset: Int = 0): List<Note> = launchIo {
|
suspend fun findAll(userId: Int, limit: Int = 20, after: UUID? = null): List<Note> = launchIo {
|
||||||
|
|
||||||
|
var previous: LocalDateTime? = null
|
||||||
|
|
||||||
|
if (after != null) {
|
||||||
|
previous = db.sequenceOf(Notes, withReferences = false)
|
||||||
|
.filter { it.userId eq userId and (it.uuid eq after) }
|
||||||
|
.mapColumns { it.updatedAt }
|
||||||
|
.firstOrNull() ?: return@launchIo emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
val notes = db.sequenceOf(Notes, withReferences = false)
|
val notes = db.sequenceOf(Notes, withReferences = false)
|
||||||
.filterColumns { it.columns - it.userId }
|
.filterColumns { it.columns - it.userId }
|
||||||
.filter { it.userId eq userId }
|
.filter {
|
||||||
|
if (previous == null) it.userId eq userId
|
||||||
|
else (it.userId eq userId) and (it.updatedAt less previous)
|
||||||
|
}
|
||||||
.sortedByDescending { it.updatedAt }
|
.sortedByDescending { it.updatedAt }
|
||||||
.take(limit).drop(offset)
|
.take(limit)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
if (notes.isEmpty()) return@launchIo emptyList()
|
if (notes.isEmpty()) return@launchIo emptyList()
|
||||||
|
|||||||
@ -10,6 +10,9 @@ val noteValidator: Validator<Note> = ValidatorBuilder.of<Note>()
|
|||||||
notNull().notBlank().lessThanOrEqual(50)
|
notNull().notBlank().lessThanOrEqual(50)
|
||||||
}
|
}
|
||||||
.konstraint(Note::tags) {
|
.konstraint(Note::tags) {
|
||||||
this.lessThanOrEqual(10)
|
lessThanOrEqual(10)
|
||||||
|
}
|
||||||
|
.konstraint(Note::content) {
|
||||||
|
notNull().notBlank()
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user