Spring tanfolyam - 3. alkalom
A mostani alkalmon a ticketing alkalmazást fogunk befejezni.
6. Fejezet - Bővítés kommenttel
Szeretnénk kommentekkel (Label) bővíteni az alkalmazásunkat, amiket jegyekhez (Ticket) írhatunk. A kommenteknek tartsuk számon a posztolóját, a tartalmát, a poszt időpontját, az esetleges frissítés időpontját, és több-több kapcsolatban álljanak a jegyekkel. Maga a komment modul legyen opcionálisan be/ki kapcsolható!
Hogy a modul be/ki kapcsolható lehessen vegyünk fel az application.properties fájlban egy változót az alábbi sor hozzáadásával (jelenleg engedélyezve van).
hu.bme.sch.kirdev.ticketingspring.load.comment=true
Ez csak egy változó, önmagában nem csinál semmit, de a későbbiekben ezt fogjuk használni arra, hogy a komment modult be/ki kapcsoljuk, a következő annotációval:
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
Ezt az annotációt el lehet helyezni osztály szinten (pl. a CommentService-en), vagy akár metódus szinten is (pl. a TicketService-ben egy olyan metódus megvalósítására, ahol lekér egy ticketet úgy, hogy a hozzá tartozó kommenteket is visszaadja), így ha a változó értéke false, akkor az adott osztály/metódus nem lesz elérhető, mintha meg sem lenne írva a kódban. Az annotáció viszont nem használható property-ken, így pl. a TicketEntity-ben nem tudunk feltételes ˙comments` property-t létrehozni.
Hozzunk létre egy új mappát comment néven és a továbbiakban oda dolgozzunk.
CommentEntity
CommentEntity.kt
@Entity
@Table(name = "comment")
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class CommentEntity(
@Id
@GeneratedValue
@Column(nullable = false)
val id: Int = 0,
@Column(nullable = false)
var postedBy: String = "",
@Column(nullable = false)
var content: String = "",
@Column(nullable = false)
val postedAt: Date = Date(),
@Column(nullable = false)
var updatedAt: Date = Date(),
@Column(nullable = false)
var ticketId: Int = 0,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CommentEntity) return false
if (id != other.id) return false
return true
}
override fun hashCode(): Int = javaClass.hashCode()
override fun toString(): String {
return this::class.simpleName + "(id = $id)"
}
@PreUpdate
fun preUpdate() {
updatedAt = Date()
}
}
CommentRepository
CommentRepository.kt
@Repository
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
interface CommentRepository: CrudRepository<CommentEntity, Int> {
fun findAllByTicketId(ticketId: Int): List<CommentEntity>
}
Comment DTO-k
CommentDtos.kt
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class CreateCommentDto(
val postedBy: String,
val content: String
)
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class CommentDto(
val id: Int,
val postedBy: String,
val content: String,
val postedAt: Date,
val updatedAt: Date,
val ticketId: Int,
)
{
constructor(comment: CommentEntity): this(
id = comment.id,
postedBy = comment.postedBy,
content = comment.content,
postedAt = comment.postedAt,
updatedAt = comment.updatedAt,
ticketId = comment.ticketId,
)
}
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class DetailedCommentDto(
val id: Int,
val postedBy: String,
val content: String,
val postedAt: Date,
val updatedAt: Date,
val ticket: TicketDto
) {
constructor(comment: CommentEntity, ticket: TicketEntity): this(
id = comment.id,
postedBy = comment.postedBy,
content = comment.content,
postedAt = comment.postedAt,
updatedAt = comment.updatedAt,
ticket = TicketDto(ticket)
)
}
CommentService
CommentService.kt
@Service
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
class CommentService(
private val commentRepository: CommentRepository,
private val ticketRepository: TicketRepository
) {
fun createComment(comment: CreateCommentDto, ticketId: Int): DetailedCommentDto {
val ticket = ticketRepository.findById(ticketId)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }
return commentRepository.save(CommentEntity(
postedBy = comment.postedBy,
content = comment.content,
ticketId = ticketId
)).let { DetailedCommentDto(it, ticket) }
}
fun getComment(id: Int): DetailedCommentDto {
return commentRepository.findById(id)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Comment not found") }
.let { comment ->
val ticket = ticketRepository.findById(comment.ticketId)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }
DetailedCommentDto(comment, ticket)
}
}
fun getAllCommentsForTicket(ticketId: Int): List<DetailedCommentDto> {
val ticket = ticketRepository.findById(ticketId)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }
return commentRepository.findAllByTicketId(ticketId).map { comment ->
DetailedCommentDto(comment, ticket)
}
}
fun updateComment(id: Int, comment: CreateCommentDto): DetailedCommentDto {
return commentRepository.findById(id).map {
it.postedBy = comment.postedBy
it.content = comment.content
commentRepository.save(it)
}.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Comment not found") }
.let { DetailedCommentDto(it, ticketRepository.findById(id)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }) }
}
fun deleteComment(id: Int) {
commentRepository.deleteById(id)
}
}
CommentController
CommentController.kt
@RestController
@RequestMapping("/")
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
class CommentController(
private val commentService: CommentService
) {
@PostMapping("/tickets/{ticketId}/comments")
fun createComment(@RequestBody comment: CreateCommentDto, @PathVariable ticketId: Int): ResponseEntity<DetailedCommentDto> {
val created = commentService.createComment(comment, ticketId)
return ResponseEntity.status(HttpStatus.CREATED).body(created)
}
@GetMapping("/comments/{id}")
fun getComment(@PathVariable id: Int): ResponseEntity<DetailedCommentDto> {
val comment = commentService.getComment(id)
return ResponseEntity.status(HttpStatus.OK).body(comment)
}
@GetMapping("/tickets/{ticketId}/comments")
fun getAllCommentsForTicket(@PathVariable ticketId: Int): ResponseEntity<List<DetailedCommentDto>> {
val comments = commentService.getAllCommentsForTicket(ticketId)
return ResponseEntity.status(HttpStatus.OK).body(comments)
}
@PatchMapping("/comments/{id}")
fun updateComment(@PathVariable id: Int, @RequestBody comment: CreateCommentDto): ResponseEntity<DetailedCommentDto> {
val updated = commentService.updateComment(id, comment)
return ResponseEntity.status(HttpStatus.OK).body(updated)
}
@DeleteMapping("/comments/{id}")
fun deleteComment(@PathVariable id: Int): ResponseEntity<Void> {
commentService.deleteComment(id)
return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}
}
Kommentek kipróbálása
Próbáljuk ki kommenteket, amihez most már tudjuk használni a Swaggert is!
localhost:8080/swagger-ui/index.html
Teszteljük le úgy is az alkalmazást, hogy a komment modul ki van kapcsolva!
Szorgalmi feladat
Szeretnénk egy új végpontot, ahol egy adott ticketet lekérve visszakapjuk a hozzá tartozó kommenteket is. Ehhez használjuk a @ConditionalOnBooleanProperty annotációt metódus szinten, hogy ez a végpont csak akkor legyen elérhető, ha a komment modul engedélyezve van.
Feladatot készítette: Szabó Benedek, Leírást készítette: Bácsi Miklós